-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
improve construction of <:Tuple
types from iterators
#53265
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# This file is a part of Julia. License is MIT: https://julialang.org/license | ||
|
||
module TupleFromIterator | ||
|
||
import ..TypeIntersectExact as TIE | ||
|
||
incremented(n::Int) = Core.Intrinsics.add_int(n, 1) | ||
decremented(n::Int) = Core.Intrinsics.sub_int(n, 1) | ||
|
||
function _totuple_err(@nospecialize T) | ||
@noinline | ||
throw(ArgumentError("too few or too many elements for tuple type $T")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this use a LazyString? |
||
end | ||
|
||
function iterator_to_ntuple_recur(::Type{Tuple{}}, iter, state::Union{Tuple{},Tuple{Any}}) | ||
i = iterate(iter, state...)::Union{Nothing,Tuple{Any,Any}} | ||
isnothing(i) || | ||
throw(ArgumentError("iterator has too many elements for the tuple type")) | ||
() | ||
end | ||
function iterator_to_ntuple_recur( | ||
::Type{Tuple{E,Vararg{E,lenm1}}}, iter, state::Union{Tuple{},Tuple{Any}} | ||
) where {E,lenm1} | ||
T2 = Tuple{E,Any} | ||
ItUn = Union{Nothing,T2} | ||
i = iterate(iter, state...)::ItUn | ||
T = Tuple{E,Vararg{E,lenm1}} | ||
isnothing(i) && | ||
throw(ArgumentError("iterator has too few elements for the tuple type")) | ||
(e, s) = i::T2 | ||
Next = NTuple{lenm1,E} | ||
t = iterator_to_ntuple_recur(Next, iter, (s,))::Next | ||
(e, t...)::T | ||
end | ||
|
||
function iterator_to_ntuple(::Type{T}, iter) where {len,T<:NTuple{len,Any}} | ||
if len < 100 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 100 seems pretty high for this. Does the recursive version really infer that well? |
||
iterator_to_ntuple_recur(T, iter, ())::T | ||
else | ||
# prevent stack overflow during type inference | ||
let | ||
f(i) = (collect(i)...,) | ||
f(i::Tuple) = i | ||
f(i::Union{NamedTuple,Core.SimpleVector,Array,Pair}) = (i...,) | ||
f(i::Array{<:Any,0}) = (first(i),) | ||
f(i::Union{AbstractArray{<:Any,0},Number,Ref}) = (first(i),) | ||
f(i::Pair) = (first(i), last(i)) | ||
f(iter)::T | ||
end | ||
end::T | ||
end | ||
|
||
function iterator_to_tuple(::Type{R}, iter) where {R<:Tuple} | ||
f(i) = collect(i) | ||
f(i::Union{Tuple,NamedTuple,Core.SimpleVector,Array,AbstractArray{<:Any,0},Number,Ref,Pair}) = i | ||
|
||
c = f(iter) | ||
len = length(c)::Int | ||
E = eltype(c)::Type | ||
T = NTuple{len,E} | ||
r = iterator_to_ntuple(T, c)::Tuple{Vararg{E}}::T | ||
(r isa R) || _totuple_err(R) | ||
r::R | ||
end | ||
|
||
""" | ||
ntuple_any(::Type{<:NTuple{len,Any}}) where {len} | ||
|
||
Like `ntuple(Returns(Any), Val(len))`. | ||
""" | ||
ntuple_any(::Type{Tuple{}}) = () | ||
function ntuple_any(::Type{<:Tuple{Any,Vararg{Any,lenm1}}}) where {lenm1} | ||
T = NTuple{lenm1,DataType} | ||
t = ntuple_any(T)::T | ||
(Any, t...)::Tuple{DataType,Vararg{DataType,lenm1}} | ||
end | ||
|
||
""" | ||
tuple_va_type_length(::Val) | ||
|
||
Creates a `Vararg` `<:Tuple` type of specified minimal length. | ||
""" | ||
function tuple_va_type_length(::Val{len}) where {len} | ||
Tuple{ntuple_any(NTuple{len,DataType})...,Vararg} | ||
end | ||
tuple_va_type_length(n::Int) = tuple_va_type_length(Val(n))::Type{<:Tuple} | ||
|
||
tuple_length_type_va(::Type{T}, ::Val{n}, ::Type{S}) where {T<:Tuple,n,S<:Tuple} = Val(decremented(n)) | ||
function tuple_length_type_va(::Type{T}, ::Val{n}, ::Type{S}) where {n,S<:Tuple,T<:S} | ||
v = Val(incremented(n)) | ||
tuple_length_type_va(T, v, tuple_va_type_length(v)::Type{<:Tuple})::Val | ||
end | ||
|
||
""" | ||
tuple_length_type(::Type{<:Tuple}) | ||
|
||
Strips the element type information from a tuple type while keeping | ||
the information about the length. | ||
""" | ||
function tuple_length_type(::Type{T}) where {T<:Tuple} | ||
v = Val(1) | ||
r = tuple_length_type_va(T, v, tuple_va_type_length(v)::Type{<:Tuple})::Val | ||
tuple_va_type_length(r)::Type{<:Tuple} | ||
end | ||
tuple_length_type(::Type{T}) where {len,T<:NTuple{len,Any}} = NTuple{len,Any} | ||
|
||
""" | ||
fieldtype_typeintersect_ntuple(::Type{T}, ::Type{<:NTuple{len,Any}}, ::Val{ind}) where {T<:Tuple, len, ind} | ||
|
||
Returns the type of the field `ind` in the type intersection of `T` with | ||
`NTuple{len,Any}`. Failing to compute the exact intersection, the field `ind` of | ||
`T` is returned. | ||
""" | ||
function fieldtype_typeintersect_ntuple( | ||
(@nospecialize T::Type{<:Tuple}), ::Type{<:NTuple{len,Any}}, ::Val{ind} | ||
) where {len,ind} | ||
Core.@_foldable_meta | ||
(1 ≤ (ind::Int) ≤ len) || throw(ArgumentError("`ind` out of bounds")) | ||
S = NTuple{len,Any} | ||
ST = typeintersect(S, T)::Type | ||
TS = typeintersect(T, S)::Type | ||
X = fieldtype(T, ind)::Type | ||
Y = fieldtype(ST, ind)::Type | ||
Z = fieldtype(TS, ind)::Type | ||
type_intrs = TIE.type_intersect_exact(X, Y, Z) | ||
TIE.get_result(X, type_intrs)::Type | ||
end | ||
|
||
function tuple_converted_elem(::Type{T}, t::NTuple{len,Any}, ::Val{ind}) where {T<:Tuple,len,ind} | ||
(1 ≤ (ind::Int) ≤ len) || throw(ArgumentError("`ind` out of bounds")) | ||
S = fieldtype_typeintersect_ntuple(T, NTuple{len,Any}, Val(ind))::Type | ||
e = t[ind] | ||
((e isa S) ? e : convert(S, e))::S | ||
end | ||
|
||
function tuple_with_converted_elems_recur(::Type{T}, ::NTuple{len,Any}, r::NTuple{len,Any}) where {T<:Tuple,len} | ||
r::T | ||
end | ||
function tuple_with_converted_elems_recur(::Type{T}, t::NTuple{len,Any}, r::NTuple{n,Any}) where {T<:Tuple,len,n} | ||
(n < len) || throw(ArgumentError("`n` out of bounds")) | ||
m = incremented(n) | ||
s = (r..., tuple_converted_elem(T, t, Val(m)))::NTuple{m,Any} | ||
tuple_with_converted_elems_recur(T, t, s)::NTuple{len,Any} | ||
end | ||
|
||
function tuple_with_converted_elems(::Type{T}, t::NTuple{len,Any}) where {T<:Tuple,len} | ||
NT = NTuple{len,Any} | ||
type_intrs = TIE.type_intersect_exact(NT, T) | ||
S = TIE.get_result(T, type_intrs)::Type{<:Tuple} | ||
(S <: Union{}) && _totuple_err(T) | ||
tuple_with_converted_elems_recur(S, t, ())::T::NT::S | ||
end | ||
|
||
function iterator_to_tuple_with_element_types(::Type{T}, iter) where {T<:Tuple} | ||
R = tuple_length_type(T)::Type{<:Tuple} | ||
t = iterator_to_tuple(R, iter)::R | ||
tuple_with_converted_elems(T, t)::T | ||
end | ||
|
||
# As an optimization, special case some tuple types with no constraints on the | ||
# types of the elements. | ||
iterator_to_tuple_with_element_types(T::Type{NTuple{len,Any}}, i) where {len} = iterator_to_tuple(T, i)::T | ||
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(0)}, i) = iterator_to_tuple(T, i)::T | ||
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(1)}, i) = iterator_to_tuple(T, i)::T | ||
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(2)}, i) = iterator_to_tuple(T, i)::T | ||
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(3)}, i) = iterator_to_tuple(T, i)::T | ||
iterator_to_tuple_with_element_types(T::Type{tuple_va_type_length(4)}, i) = iterator_to_tuple(T, i)::T | ||
|
||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# This file is a part of Julia. License is MIT: https://julialang.org/license | ||
|
||
module TypeIntersectExact | ||
|
||
struct OK end | ||
|
||
""" | ||
When `OK <: ok`, `R` is the exact type intersection. When `ok <: Union{}`, the | ||
exact type intersection wasn't found and `R` has no significance. | ||
""" | ||
struct Result{R, ok<:OK} end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fact that this is implementing an Option type seems pretty sketchy to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is it sketchy? It just seems like an elegant encoding to me? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is to let the caller know if the exact intersection couldn't be found. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is, the idea is to enable the implementation of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's relatively sensible, the weird parts are that
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it may be elegant, but something like that should be a separate PR, and not live under There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that I think about this again, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @adienes what do you mean by "not live under There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have a suggestion for a better name? |
||
|
||
result(::Type{T}) where {T} = Result{T, OK }() | ||
failure() = Result{nothing, Union{}}() | ||
|
||
""" | ||
get_result(::Type, ::Result)::Type | ||
|
||
Returns the exact type intersection if it was found, otherwise returns the | ||
fallback. | ||
""" | ||
function get_result end | ||
|
||
get_result(::Type{Fallback}, ::Result{<:Any, Union{}}) where {Fallback} = Fallback | ||
get_result(::Type{Fallback}, ::Result{R, OK }) where {Fallback, R} = R::Type{R} | ||
|
||
""" | ||
type_intersect_exact(types...)::Result | ||
|
||
Finds an exact type intersection or reports failure. | ||
""" | ||
function type_intersect_exact end | ||
|
||
type_intersect_exact() = result(Any)::Result | ||
|
||
function type_intersect_exact(@nospecialize A::Type) | ||
Core.@_foldable_meta | ||
result(A)::Result | ||
end | ||
|
||
function type_intersect_exact((@nospecialize A::Type), (@nospecialize B::Type)) | ||
Core.@_foldable_meta | ||
if A <: B | ||
result(A) | ||
elseif B <: A | ||
result(B) | ||
else | ||
let AB = typeintersect(A, B), BA = typeintersect(B, A) | ||
if (AB <: A) && (AB <: B) | ||
result(AB) | ||
elseif (BA <: A) && (BA <: B) | ||
result(BA) | ||
else | ||
failure() | ||
end | ||
end | ||
end::Result | ||
end | ||
|
||
function type_intersect_exact( | ||
(@nospecialize A::Type), (@nospecialize B::Type), (@nospecialize C::Type) | ||
) | ||
Core.@_foldable_meta # the loop below doesn't infer as terminating | ||
candidates = let | ||
AB = typeintersect(A, B) | ||
BA = typeintersect(B, A) | ||
AC = typeintersect(A, C) | ||
CA = typeintersect(C, A) | ||
BC = typeintersect(B, C) | ||
CB = typeintersect(C, B) | ||
|
||
AB_C = typeintersect(AB, C) | ||
C_AB = typeintersect(C, AB) | ||
BA_C = typeintersect(BA, C) | ||
C_BA = typeintersect(C, BA) | ||
AC_B = typeintersect(AC, B) | ||
B_AC = typeintersect(B, AC) | ||
CA_B = typeintersect(CA, B) | ||
B_CA = typeintersect(B, CA) | ||
BC_A = typeintersect(BC, A) | ||
A_BC = typeintersect(A, BC) | ||
CB_A = typeintersect(CB, A) | ||
A_CB = typeintersect(A, CB) | ||
Comment on lines
+65
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't you simplify this to something like
|
||
|
||
( | ||
A, B, C, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you're saying that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes. If it didn't, it would be doing a really bad job of returning an intersection. |
||
AB, BA, AC, CA, BC, CB, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here. |
||
AB_C, C_AB, BA_C, C_BA, AC_B, B_AC, CA_B, B_CA, BC_A, A_BC, CB_A, A_CB | ||
) | ||
end | ||
for T ∈ candidates | ||
is_exact = (T <: A) && (T <: B) && (T <: C) | ||
is_exact && (return result(T)::Result) | ||
end | ||
failure()::Result | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be a breaking change. We cannot merge the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not at all sure what you mean? It's just an internal helper function, given that it's not a change it can't be a breaking change?? Do you just don't like the name? The documentation is clear about the search for the exact intersection not always succeeding, but even so it's safe given that the result says whether the intersection was found or not. |
||
end | ||
|
||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this really need to exist this early? The code would be a lot cleaner if it could be moved to after
int
(so you can use+
)