Skip to content

Commit d4127a7

Browse files
committed
improve construction of <:Tuple types from iterators
Make tuple construction from overlong iterators throw instead of truncating the input iterator. Trying to construct a tuple from an infinite iterator now throws an `ArgumentError` instead of succeeding or throwing a `MethodError`. Fixes JuliaLang#40495 Fixes JuliaLang#52657 Make tuple construction from known constant-length iterators inferrable and prevent allocation. Fixes JuliaLang#52993 Compute more accurate tuple element types in many cases using `typeintersect`, with a fallback to the old behavior. This prevents some cases of `convert` throwing due to ambiguity. Instead of just calling `typeintersect` on the tuple types, do `typeintersect` again once `fieldtype` computes the element type. Fixes JuliaLang#53181 Separate the tuple construction from tuple element conversion, allowing dropping the `tuple_type_tail` dependency. Fixes JuliaLang#53182
1 parent 9523361 commit d4127a7

File tree

8 files changed

+554
-54
lines changed

8 files changed

+554
-54
lines changed

base/Base.jl

+2
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ include("ctypes.jl")
169169
include("gcutils.jl")
170170
include("generator.jl")
171171
include("reflection.jl")
172+
include("type_intersect_exact.jl")
172173
include("options.jl")
173174

174175
# define invoke(f, T, args...; kwargs...), without kwargs wrapping
@@ -196,6 +197,7 @@ end
196197

197198
# core operations & types
198199
include("promotion.jl")
200+
include("tuple_from_iterator.jl")
199201
include("tuple.jl")
200202
include("expr.jl")
201203
include("pair.jl")

base/tuple.jl

+4-48
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,6 @@ const Any32{N} = Tuple{Any,Any,Any,Any,Any,Any,Any,Any,
362362
Any,Any,Any,Any,Any,Any,Any,Any,
363363
Any,Any,Any,Any,Any,Any,Any,Any,
364364
Vararg{Any,N}}
365-
const All32{T,N} = Tuple{T,T,T,T,T,T,T,T,
366-
T,T,T,T,T,T,T,T,
367-
T,T,T,T,T,T,T,T,
368-
T,T,T,T,T,T,T,T,
369-
Vararg{T,N}}
370365
function map(f, t::Any32)
371366
n = length(t)
372367
A = Vector{Any}(undef, n)
@@ -449,52 +444,13 @@ end
449444

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

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-
455-
(::Type{T})(itr) where {T<:Tuple} = _totuple(T, itr)
456-
457-
_totuple(::Type{Tuple{}}, itr, s...) = ()
458-
459-
function _totuple_err(@nospecialize T)
460-
@noinline
461-
throw(ArgumentError("too few elements for tuple type $T"))
462-
end
463-
464-
function _totuple(::Type{T}, itr, s::Vararg{Any,N}) where {T,N}
465-
@inline
466-
y = iterate(itr, s...)
467-
y === nothing && _totuple_err(T)
468-
T1 = fieldtype(T, 1)
469-
y1 = y[1]
470-
t1 = y1 isa T1 ? y1 : convert(T1, y1)::T1
471-
# inference may give up in recursive calls, so annotate here to force accurate return type to be propagated
472-
rT = tuple_type_tail(T)
473-
ts = _totuple(rT, itr, y[2])::rT
474-
return (t1, ts...)::T
475-
end
476-
477-
# use iterative algorithm for long tuples
478-
function _totuple(T::Type{All32{E,N}}, itr) where {E,N}
479-
len = N+32
480-
elts = collect(E, Iterators.take(itr,len))
481-
if length(elts) != len
482-
_totuple_err(T)
447+
function (::Type{T})(iter::S) where {T<:Tuple,S}
448+
if IteratorSize(S) == IsInfinite()
449+
throw(ArgumentError("can't construct a tuple from an infinite iterator"))
483450
end
484-
(elts...,)
451+
TupleFromIterator.iterator_to_tuple_with_element_types(T, iter)::T
485452
end
486453

487-
_totuple(::Type{Tuple{Vararg{E}}}, itr, s...) where {E} = (collect(E, Iterators.rest(itr,s...))...,)
488-
489-
_totuple(::Type{Tuple}, itr, s...) = (collect(Iterators.rest(itr,s...))...,)
490-
491-
# for types that `apply` knows about, just splatting is faster than collecting first
492-
_totuple(::Type{Tuple}, itr::Array) = (itr...,)
493-
_totuple(::Type{Tuple}, itr::SimpleVector) = (itr...,)
494-
_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
497-
498454
end
499455

500456
## find ##

base/tuple_from_iterator.jl

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
module TupleFromIterator
4+
5+
import ..TypeIntersectExact as TIE
6+
7+
incremented(n::Int) = Core.Intrinsics.add_int(n, 1)
8+
decremented(n::Int) = Core.Intrinsics.sub_int(n, 1)
9+
10+
function _totuple_err(@nospecialize T)
11+
@noinline
12+
throw(ArgumentError("too few or too many elements for tuple type $T"))
13+
end
14+
15+
function iterator_to_ntuple_recur(::Type{Tuple{}}, iter, state::Union{Tuple{},Tuple{Any}})
16+
i = iterate(iter, state...)::Union{Nothing,Tuple{Any,Any}}
17+
isnothing(i) ||
18+
throw(ArgumentError("iterator has too many elements for the tuple type"))
19+
()
20+
end
21+
function iterator_to_ntuple_recur(
22+
::Type{Tuple{E,Vararg{E,lenm1}}}, iter, state::Union{Tuple{},Tuple{Any}}
23+
) where {E,lenm1}
24+
T2 = Tuple{E,Any}
25+
ItUn = Union{Nothing,T2}
26+
i = iterate(iter, state...)::ItUn
27+
T = Tuple{E,Vararg{E,lenm1}}
28+
isnothing(i) &&
29+
throw(ArgumentError("iterator has too few elements for the tuple type"))
30+
(e, s) = i::T2
31+
Next = NTuple{lenm1,E}
32+
t = iterator_to_ntuple_recur(Next, iter, (s,))::Next
33+
(e, t...)::T
34+
end
35+
36+
function iterator_to_ntuple(::Type{T}, iter) where {len,T<:NTuple{len,Any}}
37+
if len < 100
38+
iterator_to_ntuple_recur(T, iter, ())::T
39+
else
40+
# prevent stack overflow during type inference
41+
let
42+
f(i) = (collect(i)...,)
43+
f(i::Tuple) = i
44+
f(i::Union{NamedTuple,Core.SimpleVector,Array,Pair}) = (i...,)
45+
f(i::Array{<:Any,0}) = (first(i),)
46+
f(i::Union{AbstractArray{<:Any,0},Number,Ref}) = (first(i),)
47+
f(i::Pair) = (first(i), last(i))
48+
f(iter)::T
49+
end
50+
end::T
51+
end
52+
53+
function iterator_to_tuple(::Type{R}, iter) where {R<:Tuple}
54+
f(i) = collect(i)
55+
f(i::Union{Tuple,NamedTuple,Core.SimpleVector,Array,AbstractArray{<:Any,0},Number,Ref,Pair}) = i
56+
57+
c = f(iter)
58+
len = length(c)::Int
59+
E = eltype(c)::Type
60+
T = NTuple{len,E}
61+
r = iterator_to_ntuple(T, c)::Tuple{Vararg{E}}::T
62+
(r isa R) || _totuple_err(R)
63+
r::R
64+
end
65+
66+
"""
67+
ntuple_any(::Type{<:NTuple{len,Any}}) where {len}
68+
69+
Like `ntuple(Returns(Any), Val(len))`.
70+
"""
71+
ntuple_any(::Type{Tuple{}}) = ()
72+
function ntuple_any(::Type{<:Tuple{Any,Vararg{Any,lenm1}}}) where {lenm1}
73+
T = NTuple{lenm1,DataType}
74+
t = ntuple_any(T)::T
75+
(Any, t...)::NTuple{len,DataType}
76+
end
77+
78+
"""
79+
tuple_va_type_length(::Val)
80+
81+
Creates a `Vararg` `<:Tuple` type of specified minimal length.
82+
"""
83+
function tuple_va_type_length(::Val{len}) where {len}
84+
Tuple{ntuple_any(NTuple{len,DataType})...,Vararg}
85+
end
86+
tuple_va_type_length(n::Int) = tuple_va_type_length(Val(n))::Type{<:Tuple}
87+
88+
tuple_length_type_va(::Type{T}, ::Val{n}, ::Type{S}) where {T<:Tuple,n,S<:Tuple} = Val(decremented(n))
89+
function tuple_length_type_va(::Type{T}, ::Val{n}, ::Type{S}) where {n,S<:Tuple,T<:S}
90+
v = Val(incremented(n))
91+
tuple_length_type_va(T, v, tuple_va_type_length(v)::Type{<:Tuple})::Val
92+
end
93+
94+
"""
95+
tuple_length_type(::Type{<:Tuple})
96+
97+
Strips the element type information from a tuple type while keeping
98+
the information about the length.
99+
"""
100+
function tuple_length_type(::Type{T}) where {T<:Tuple}
101+
v = Val(1)
102+
r = tuple_length_type_va(T, v, tuple_va_type_length(v)::Type{<:Tuple})::Val
103+
tuple_va_type_length(r)::Type{<:Tuple}
104+
end
105+
tuple_length_type(::Type{T}) where {len,T<:NTuple{len,Any}} = NTuple{len,Any}
106+
107+
"""
108+
fieldtype_typeintersect_ntuple(::Type{T}, ::Type{<:NTuple{len,Any}}, ::Val{ind}) where {T<:Tuple, len, ind}
109+
110+
Returns the type of the field `ind` in the type intersection of `T` with
111+
`NTuple{len,Any}`. Failing to compute the exact intersection, the field `ind` of
112+
`T` is returned.
113+
"""
114+
function fieldtype_typeintersect_ntuple(
115+
(@nospecialize T::Type{<:Tuple}), ::Type{<:NTuple{len,Any}}, ::Val{ind}
116+
) where {len,ind}
117+
Core.@_foldable_meta
118+
(1 (ind::Int) len) || throw(ArgumentError("`ind` out of bounds"))
119+
S = NTuple{len,Any}
120+
ST = typeintersect(S, T)::Type
121+
TS = typeintersect(T, S)::Type
122+
X = fieldtype(T, ind)::Type
123+
Y = fieldtype(ST, ind)::Type
124+
Z = fieldtype(TS, ind)::Type
125+
type_intrs = TIE.type_intersect_exact(X, Y, Z)
126+
TIE.get_result(X, type_intrs)::Type
127+
end
128+
129+
function tuple_converted_elem(::Type{T}, t::NTuple{len,Any}, ::Val{ind}) where {T<:Tuple,len,ind}
130+
(1 (ind::Int) len) || throw(ArgumentError("`ind` out of bounds"))
131+
S = fieldtype_typeintersect_ntuple(T, NTuple{len,Any}, Val(ind))::Type
132+
e = t[ind]
133+
((e isa S) ? e : convert(S, e))::S
134+
end
135+
136+
function tuple_with_converted_elems_recur(::Type{T}, ::NTuple{len,Any}, r::NTuple{len,Any}) where {T<:Tuple,len}
137+
r::T
138+
end
139+
function tuple_with_converted_elems_recur(::Type{T}, t::NTuple{len,Any}, r::NTuple{n,Any}) where {T<:Tuple,len,n}
140+
(n < len) || throw(ArgumentError("`n` out of bounds"))
141+
m = incremented(n)
142+
s = (r..., tuple_converted_elem(T, t, Val(m)))::NTuple{m,Any}
143+
tuple_with_converted_elems_recur(T, t, s)::NTuple{len,Any}
144+
end
145+
146+
function tuple_with_converted_elems(::Type{T}, t::NTuple{len,Any}) where {T<:Tuple,len}
147+
NT = NTuple{len,Any}
148+
type_intrs = TIE.type_intersect_exact(NT, T)
149+
S = TIE.get_result(T, type_intrs)::Type{<:Tuple}
150+
(S <: Union{}) && _totuple_err(T)
151+
tuple_with_converted_elems_recur(S, t, ())::T::NT::S
152+
end
153+
154+
function iterator_to_tuple_with_element_types(::Type{T}, iter) where {T<:Tuple}
155+
R = tuple_length_type(T)::Type{<:Tuple}
156+
t = iterator_to_tuple(R, iter)::R
157+
tuple_with_converted_elems(T, t)::T
158+
end
159+
160+
# As an optimization, special case some tuple types with no constraints on the
161+
# types of the elements.
162+
iterator_to_tuple_with_element_types(T::Type{NTuple{len,Any}}, i) where {len} = iterator_to_tuple(T, i)::T
163+
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(0)}, i) = iterator_to_tuple(T, i)::T
164+
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(1)}, i) = iterator_to_tuple(T, i)::T
165+
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(2)}, i) = iterator_to_tuple(T, i)::T
166+
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(3)}, i) = iterator_to_tuple(T, i)::T
167+
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(4)}, i) = iterator_to_tuple(T, i)::T
168+
169+
end

base/type_intersect_exact.jl

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
module TypeIntersectExact
4+
5+
struct OK end
6+
7+
"""
8+
When `OK <: ok`, `R` is the exact type intersection. When `ok <: Union{}`, the
9+
exact type intersection wasn't found and `R` has no significance.
10+
"""
11+
struct Result{R, ok<:OK} end
12+
13+
result(::Type{T}) where {T} = Result{T, OK }()
14+
failure() = Result{nothing, Union{}}()
15+
16+
"""
17+
get_result(::Type, ::Result)::Type
18+
19+
Returns the exact type intersection if it was found, otherwise returns the
20+
fallback.
21+
"""
22+
function get_result end
23+
24+
get_result(::Type{Fallback}, ::Result{<:Any, Union{}}) where {Fallback} = Fallback
25+
get_result(::Type{Fallback}, ::Result{R, OK }) where {Fallback, R} = R::Type{R}
26+
27+
"""
28+
type_intersect_exact(types...)::Result
29+
30+
Finds an exact type intersection or reports failure.
31+
"""
32+
function type_intersect_exact end
33+
34+
type_intersect_exact() = result(Any)::Result
35+
36+
function type_intersect_exact(@nospecialize A::Type)
37+
Core.@_foldable_meta
38+
result(A)::Result
39+
end
40+
41+
function type_intersect_exact((@nospecialize A::Type), (@nospecialize B::Type))
42+
Core.@_foldable_meta
43+
if A <: B
44+
result(A)
45+
elseif B <: A
46+
result(B)
47+
else
48+
let AB = typeintersect(A, B), BA = typeintersect(B, A)
49+
if (AB <: A) && (AB <: B)
50+
result(AB)
51+
elseif (BA <: A) && (BA <: B)
52+
result(BA)
53+
else
54+
failure()
55+
end
56+
end
57+
end::Result
58+
end
59+
60+
function type_intersect_exact(
61+
(@nospecialize A::Type), (@nospecialize B::Type), (@nospecialize C::Type)
62+
)
63+
Core.@_foldable_meta # the loop below doesn't infer as terminating
64+
candidates = let
65+
AB = typeintersect(A, B)
66+
BA = typeintersect(B, A)
67+
AC = typeintersect(A, C)
68+
CA = typeintersect(C, A)
69+
BC = typeintersect(B, C)
70+
CB = typeintersect(C, B)
71+
72+
AB_C = typeintersect(AB, C)
73+
C_AB = typeintersect(C, AB)
74+
BA_C = typeintersect(BA, C)
75+
C_BA = typeintersect(C, BA)
76+
AC_B = typeintersect(AC, B)
77+
B_AC = typeintersect(B, AC)
78+
CA_B = typeintersect(CA, B)
79+
B_CA = typeintersect(B, CA)
80+
BC_A = typeintersect(BC, A)
81+
A_BC = typeintersect(A, BC)
82+
CB_A = typeintersect(CB, A)
83+
A_CB = typeintersect(A, CB)
84+
85+
(
86+
A, B, C,
87+
AB, BA, AC, CA, BC, CB,
88+
AB_C, C_AB, BA_C, C_BA, AC_B, B_AC, CA_B, B_CA, BC_A, A_BC, CB_A, A_CB
89+
)
90+
end
91+
for T candidates
92+
is_exact = (T <: A) && (T <: B) && (T <: C)
93+
is_exact && (return result(T)::Result)
94+
end
95+
failure()::Result
96+
end
97+
98+
end

test/ambiguous.jl

-1
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,6 @@ end
345345
end
346346
let need_to_handle_undef_sparam =
347347
Set{Method}(detect_unbound_args(Base; recursive=true, allowed_undefineds))
348-
pop!(need_to_handle_undef_sparam, which(Base._totuple, (Type{Tuple{Vararg{E}}} where E, Any, Any)))
349348
pop!(need_to_handle_undef_sparam, which(Base.eltype, Tuple{Type{Tuple{Any}}}))
350349
pop!(need_to_handle_undef_sparam, first(methods(Base.same_names)))
351350
@test_broken need_to_handle_undef_sparam == Set()

test/arrayops.jl

-1
Original file line numberDiff line numberDiff line change
@@ -2102,7 +2102,6 @@ end
21022102
@test CartesianIndex{3}(1,2,3)*2 == CartesianIndex{3}(2,4,6)
21032103
@test_throws ErrorException iterate(CartesianIndex{3}(1,2,3))
21042104
@test CartesianIndices(CartesianIndex{3}(1,2,3)) == CartesianIndices((1, 2, 3))
2105-
@test Tuple{}(CartesianIndices{0,Tuple{}}(())) == ()
21062105

21072106
R = CartesianIndices(map(Base.IdentityUnitRange, (2:5, 3:5)))
21082107
@test eltype(R) <: CartesianIndex{2}

test/core.jl

-2
Original file line numberDiff line numberDiff line change
@@ -6883,8 +6883,6 @@ let a = Foo17149()
68836883
end
68846884

68856885
# issue #21004
6886-
const PTuple_21004{N,T} = NTuple{N,VecElement{T}}
6887-
@test_throws ArgumentError("too few elements for tuple type $PTuple_21004") PTuple_21004(1)
68886886
@test_throws UndefVarError(:T, :static_parameter) PTuple_21004_2{N,T} = NTuple{N, VecElement{T}}(1)
68896887

68906888
#issue #22792

0 commit comments

Comments
 (0)