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

**URL:** <https://discourse.julialang.org/t/notice-a-subtle-footgun-unionall-type-variable-order-method-static-parameter-normalization/113779>\
**Category:** General Usage\
**Tags:** type, parametric-types, parametric-methods, wat, footgun\
**Created:** [May 3, 2024, 8:33am UTC](https://discourse.julialang.org/t/notice-a-subtle-footgun-unionall-type-variable-order-method-static-parameter-normalization/113779 "2024-05-03T08:33:29Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [May 3, 2024, 8:33am UTC](https://discourse.julialang.org/t/notice-a-subtle-footgun-unionall-type-variable-order-method-static-parameter-normalization/113779/1 "2024-05-03T08:33:29Z")

</div>

Consider this snippet:

```julia
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 [`==`](https://docs.julialang.org/en/v1/base/math/#Base.:==)-sense, but _not_ in the [`===`](https://docs.julialang.org/en/v1/base/base/#Core.:===)-sense. And `==` between [`UnionAll`](https://docs.julialang.org/en/v1/manual/types/#UnionAll-Types) types isn’t affected by type variable order. Thus:

```julia-repl
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-repl
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

```

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [May 3, 2024, 11:02am UTC](https://discourse.julialang.org/t/notice-a-subtle-footgun-unionall-type-variable-order-method-static-parameter-normalization/113779/2 "2024-05-03T11:02:56Z")

</div>

> [@nsajko](#):
>
> `id(some_unionall)` effectively normalizes the type

Seems to happen right at parameterization:

```julia-auto
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-auto
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-auto
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

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [January 31, 2025, 10:12am UTC](https://discourse.julialang.org/t/notice-a-subtle-footgun-unionall-type-variable-order-method-static-parameter-normalization/113779/3 "2025-01-31T10:12:47Z")

</div>

Why does normalization not happen here for `Type{b}`, even though it does happen for `Val{b}`?

```julia-repl
julia> b = Tuple{S} where {S <: String}
Tuple{S} where S<:String

julia> Val{b} # normalization happens
Val{Tuple{String}}

julia> Type{b} # normalization does *not* happen
Type{Tuple{S} where S<:String}

```

I wonder if that’s intentional.
