Need help understanding Type parameters and Vararg tuples

Hello!

I’m encountering quite an headscratcher, I’ve given it quite some time but I couldn’t crack. I am struggling to understand why a tuple of two types is not being recognized as such, while all the intermediate steps are successfull.

Here is a MWE

abstract type A end
abstract type B end
abstract type C{TA<:A, TB<:B} end

struct a <: A end
struct b <: B end
struct c1{TA <: A, TB <: B} <: C{TA, TB}
	ta::TA
	tb::TB
	e::Int
end
struct c2{TA<: A, TB <: B} <: C{TA, TB} 
	ta::TA
	tb::TB
	e::Float 
end

e1 = c1(a(), b(), 1)
e2 = c2(a(), b(), 1.0)

e1 isa TC where {TA <: A, TB <: B, TC <: C{TA, TB}}
# -> true
e2 isa TC where {TA <: A, TB <: B, TC <: C{TA, TB}} 
# -> true
# great!, then i can do
(e1, e2) isa Tuple{Vararg{<: C{TA, TB}}} where {TA <: A, TB <: B} 
# -> true

# therefore also
(e1, e2) isa Tuple{Vararg{TC}} where {TA <: A, TB <: B, TC <: C{TA, TB}} 
# -> false (???) huh?

#but
[e1, e2] isa Vector{TC} where {TA <: A, TB <: B, TC <: C{TA, TB}} 
# -> true

I’ve read the manual on types multiple times but for the life of me I couldn’t understand what is going on here. Why is that second to last test false??

I think this is the diagonal rule?

It turns out that being able to dispatch on whether two values have the same type is very useful (this is used by the promotion system for example), so we have multiple reasons to want a different interpretation of Tuple{T,T} where T. To make this work we add the following rule to subtyping: if a variable occurs more than once in covariant position, it is restricted to ranging over only concrete types. (“Covariant position” means that only Tuple and Union types occur between an occurrence of a variable and the UnionAll type that introduces it.) Such variables are called “diagonal variables” or “concrete variables”.
More about types · The Julia Language

It’s not about Vararg, but about using the the same type variable in more than one place (which I think Vararg implicitly does):

julia> (e1,e2) isa Tuple{TC,TC} where {TC <: C}
false

julia> (e1,e2) isa Tuple{TC, <:TC} where {TC <: C}
true

julia> (e1,e2) isa Tuple{TC,TD} where {TC <: C, TD <: C}
true
1 Like

Thanks a million!
Didn’t think of checking the “internals” section of the manual.
After reading that section I must admit I didn’t completely understand it all
but I understood enough to come up with

(e1, e2) isa Tuple{Vararg{TC} where TC} where {TA <: A, TB <: B, TC <: C{TA, TB}}
# -> true

hoping that this Vararg{TC} where TC doesn’t introduce any extra edge case!

If I understood this correctly that extra where TC is making all the TCs in the Vararg bound to a single variable in covariant position which makes it possible to range also over non concrete types, such as C{TA <: A, TB <: B}, is this correct?

Uhm, No.
Tuple{Vararg{G} where G} works in the isa expression but when used in a function declaration or struct it complains.
Why is that?