== seems to work given the one type, though I don’t see this documented for iterated unions in particular.
julia> let sig = Tuple{typeof(sin),Float64}, T = Type
(T{sig} == T{S} where {S<:sig},
T{sig} == T{S} where {sig<:S<:sig}) # force S == sig
end
(false, true)
It’s not, one strictly subtypes the other:
julia> let sig = Tuple{typeof(sin),Float64}, T = Type
(Union{T{sig}, T{Union{}}} == T{<:sig},
Union{T{sig}, T{Union{}}} <: T{<:sig},
T{<:sig} <: Union{T{sig}, T{Union{}}})
end
(false, true, false)
Can’t imagine what else could be in T{<:sig} though. Results are the same for sig = Int, T = Vector so it’s not an artifact of Tuple or Type.
I wonder if the question is easier for us to answer if we restrict ourselves to a subset of possible Tuples? For context: in my particular use case, there’s a performance optimisation that I can perform if I know that Type{T} where T<:Tuple{...} contains only Type{Tuple{...}} and Type{Union{}}, so I’m quite happy if we’re able to determine if this is the case for a large-ish family of Tuple{...}s, as opposed to all possible Tuple{...}s.
For example, given Tuple{P_1,P_2,P_3...} where each P_n is not a subtype of Type (because this contains some edge cases that I find hard to reason about), is it enough to check that all P_n are concrete?
This makes sense. There’s still the edge case involving something like Tuple{Type{F}} for some type F to deal with, so perhaps something like this would work?
function is_safe_to_optimise(x::Type{<:Tuple})
# Exclude `UnionAll`s, `Union`s, and `Core.TypeofBottom`.
x isa DataType || return false
try
# Exclude eg. `Tuple{DataType}` because `DataType` is concrete, but `Type{F} <: DataType`.
any(T -> T <: Type, fieldtypes(x)) && return false
# It's now enough to know that everything is concrete?
return isconcretetype(x)
catch
# If contains a `Vararg`, the fieldcount will not be defined, and an `ArgumentError` thrown.
return false
end
end