# Notice: a subtle footgun: UnionAll type variable order + method static parameter normalization

Consider this snippet:

``````id(::Type{T}) where {T} = T
some_type{7,Int} == id(some_type){7,Int}
``````

Naively, it seems like the comparison on the second line should return true for any type `some_type`, assuming neither type application throws. After all, `id` is the identity function, so the second line should be equivalent to the obvious tautology `some_type{7,Int} == some_type{7,Int}`, right?

Well, `id` is the identity in the `==`-sense, but not in the `===`-sense. And `==` between `UnionAll` types isn’t affected by type variable order. Thus:

``````julia> some_type = AbstractArray{T,N} where {N,T}  # note the reversed type variable order
AbstractArray{T, N} where {N, T}

julia> id(::Type{T}) where {T} = T
id (generic function with 1 method)

julia> some_type{7,Int} == id(some_type){7,Int}
false

julia> some_type == id(some_type)
true

julia> some_type === id(some_type)
false

julia> AbstractArray === id(some_type)
true

julia> some_type === identity(some_type)
true
``````

So `id(some_unionall)` effectively normalizes the type, and thus the unionall type variable order. I guess my conclusion here is to take care to do the normalization as soon as possible, to prevent shooting myself in the foot.

EDIT: a similar example:

``````julia> T = AbstractArray{T,N} where {N,T}
AbstractArray{T, N} where {N, T}

julia> f(::Type{S}) where {S<:T} = S === T
f (generic function with 1 method)

julia> f(T)
false
``````
1 Like

Seems to happen right at parameterization:

``````julia> some_type = AbstractArray{T,N} where {N,T}
AbstractArray{T, N} where {N, T}

julia> Type{some_type}
Type{AbstractArray}

julia> Vector{some_type} # any parametric type does this
Vector{AbstractArray} (alias for Array{AbstractArray, 1})

julia> id(::Type{some_type}) = some_type;

julia> methods(id)
# 1 method for generic function "id" from Main:
[1] id(::Type{AbstractArray})
@ REPL[11]:1

julia> id(AbstractArray) # well that's weird
AbstractArray{T, N} where {N, T}

julia> identity(AbstractArray) # phew, thanks to identity(@nospecialize x)
AbstractArray

julia> identity(some_type) # phew: the sequel
AbstractArray{T, N} where {N, T}
``````

Maybe it’s worth clarifying in the type parameter docs that parameters are normalized for whatever reason, or whether that’s even a language semantic or a fringe type implementation detail.

EDIT: I’m pretty sure concrete types can’t be `==` and `!==`, but evidently not all such abstract types get normalized as parameters. Might be nice to know which and why.

``````julia> Tuple{Union{Int,Bool}} == Union{Tuple{Int}, Tuple{Bool}}
true

julia> Tuple{Union{Int,Bool}} === Union{Tuple{Int}, Tuple{Bool}}
false

julia> Type{Tuple{Union{Int,Bool}}}
Type{Tuple{Union{Bool, Int64}}}

julia> Type{Union{Tuple{Int}, Tuple{Bool}}}
Type{Union{Tuple{Bool}, Tuple{Int64}}}
``````

Normalization also happens at method argument annotations, maybe because they are parameters of an underlying tuple for the method?

``````julia> foo(::some_type) = 0
foo (generic function with 1 method)

julia> methods(foo)
# 1 method for generic function "foo" from Main:
[1] foo(::AbstractArray)
@ REPL[4]:1
``````
1 Like