Skip to content

Commit 8e8d931

Browse files
committed
improve construction of <:Tuple types from iterators
Make tuple construction from overlong iterators throw instead of truncating the input iterator. 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 Trying to construct a tuple from an infinite iterator now results in an `ArgumentError` instead of in a `MethodError`.
1 parent 9523361 commit 8e8d931

File tree

6 files changed

+547
-52
lines changed

6 files changed

+547
-52
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

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