# Weirdness of \`Type{Union{A,B}} where {B\<:SuperB}\`

**URL:** <https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234>\
**Category:** General Usage\
**Tags:** question, type, parametric-types\
**Created:** [June 26, 2025, 12:55pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234 "2025-06-26T12:55:59Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [June 26, 2025, 12:55pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/1 "2025-06-26T12:55:59Z")

</div>

I think my mental model of `Type{Union{A,B}}` is incorrect because I truly do not understand the following behavior:

```julia
julia> struct Foo end

julia> Foo isa (Type{Union{Foo,T}} where {T<:Base.IEEEFloat})
true

julia> Float32 isa (Type{Union{Foo,T}} where {T<:Base.IEEEFloat})
false

julia> Union{Foo,Float32} isa (Type{Union{Foo,T}} where {T<:Base.IEEEFloat})
true

```

Does anybody know what `Type{Union{A,B}} where {B<:SuperB}` stands for?

This is specifically cases where `A` is _NOT_ a member of `SuperB` and hence I would not expect any unbound type parameters, because `A` by itself should not be a subtype of `Type{Union{A,B}}`.

It might make sense if this was expanded to `Type{<:Union{A,B}}`. But then I don’t understand why the union type is a member.

If this only matched `Union{A,<:SuperB}` explicitly, then I don’t understand why `A` matches.

cc @yebai @penelopeysm

---

<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:** [June 26, 2025, 2:19pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/2 "2025-06-26T14:19:30Z")

</div>

Here’s a breakdown to explain each of your examples:

```julia-repl
julia> struct Foo end

julia> S = Union{Foo, T} where {T<:Base.IEEEFloat}
Union{Foo, T} where T<:Union{Float16, Float32, Float64}

julia> T = Type{S{T}} where {T<:Base.IEEEFloat}
Type{Union{Foo, T}} where T<:Union{Float16, Float32, Float64}

julia> Foo isa T # the first query in the OP, `true` because the following is `true`:
true

julia> Type{Foo} == T{Union{}} # `true` because the following is `true`:
true

julia> Foo == S{Union{}}
true

julia> Float32 isa T # the second query in the OP, `false` because there is no `X` such that `Type{Float32} == T{X}`, which is because there is no `X` such that `Float32 == S{X}`
false

julia> Union{Foo, Float32} isa T # the third query in the OP, `true` because the following is `true`:
true

julia> Type{Union{Foo, Float32}} == T{Float32} # `true` because the following is `true`:
true

julia> Union{Foo, Float32} == S{Float32}
true

```

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [June 26, 2025, 3:10pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/3 "2025-06-26T15:10:12Z")

</div>

Thanks!

Is there any way I can write this:

```julia
Union{Type{Union{Foo,Float16}},Type{Union{Foo,Float32}},Type{Union{Foo,Float64}}}

```

in a more efficient way using a `where` clause?

---

<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:** [June 26, 2025, 3:26pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/4 "2025-06-26T15:26:34Z")

</div>

> [@MilesCranmer](#):
>
> in a more efficient way using a `where` clause?

- what do you want when you say “more efficient”? Perhaps you’d be satisfied by something like this:

- Why do you want to use `UnionAll` (“a `where` clause”)? “Compression” (so to speak) of some `Union` with `UnionAll` will almost always, except in trivial cases, I think, produce a strict superset. I don’t know if that’s what you want. Could you say more about the subtyping (or other) properties of the type you’re looking for.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [June 26, 2025, 3:36pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/5 "2025-06-26T15:36:05Z")

</div>

All I meant by “efficient” was if there was any way of repositioning the where clause that would generate `Union{Type{Union{Foo,Float16}},...` without me needing to write it out. But yeah seems there is not. I guess the difficulty is due the special behavior of `Union`!

I was asking for this: [Support for `Union{Nothing, \<:IEEEFloat}` by yebai · Pull Request #620 · chalk-lab/Mooncake.jl · GitHub](https://github.com/chalk-lab/Mooncake.jl/pull/620). We were all surprised the `Type{Union{A,B}} where {B}` didn’t work but I guess in retrospect it makes sense.

Luckily we don’t have to match all `T<:AbstractFloat` so the solution is to just loop over the three Base.IEEEFloat.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [June 26, 2025, 3:40pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/6 "2025-06-26T15:40:31Z")

</div>

Actually we do need to match

```julia
function tangent_type(::Type{Union{NoFData,Array{T,N}}}, ::Type{NoRData}) where {T<:Base.IEEEFloat,N}
    return Union{NoTangent,tangent_type(Array{T,N})}
end

```

~~I guess this might not be possible without also matching `NoFData`~~

~~Edit 1: Actually maybe it’s fine?? I guess `isa` injects a `Union{}` into all `UnionAll` but method dispatch does not?~~

Edit 2: [Am very confused]

---

<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:** [June 26, 2025, 4:00pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/7 "2025-06-26T16:00:34Z")

</div>

> [@MilesCranmer](#):
>
> I guess the difficulty is due the special behavior of `Union`!

What do you mean by “special behavior”?

> [@MilesCranmer](#):
>
> the solution is to just loop over the three Base.IEEEFloat.

No, that’s horrible. Instead of creating unnecessary duplicated methods, just use `Union`.

> [@MilesCranmer](#):
>
> Actually we do need to match
> 
> ```julia
> function tangent_type(::Type{Union{NoFData,Array{T,N}}}, ::Type{NoRData}) where {T<:Base.IEEEFloat,N}
> return Union{NoTangent,tangent_type(Array{T,N})}
> end
> 
> ```
> 
> ~~I guess this might not be possible without also matching `NoFData`~~
> 
> ~~Edit 1: Actually maybe it’s fine?? I guess `isa` injects a `Union{}` into all `UnionAll` but method dispatch does not?~~
> 
> Edit 2: [Am very confused]

I don’t understand this last message at all. Keep in mind I’m not familiar with the internals of your package.

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [June 26, 2025, 4:14pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/8 "2025-06-26T16:14:34Z")

</div>

> [@nsajko](#):
>
> > [@MilesCranmer](#):
> >
> > the solution is to just loop over the three Base.IEEEFloat.
> 
> No, that’s horrible. Instead of creating unnecessary duplicated methods, just use `Union`.

All I meant was the same thing you wrote above:

> [@nsajko](#):
>
> ```julia-auto
> let f(@nospecialize x::Type) = Type{Union{Foo, x}}
> types = map(f, (Float16, Float32, Float64))
> Union{types...}
> end
> 
> ```

Which we can do for Base.IEEEFloat (because it’s an explicit union that we can loop through), but we seemingly can’t do for abstract types like AbstractFloat without an ambiguous form

```julia
foo(::Type{Union{Foo,B}}) where {B<:Base.IEEEFloat} = B

```

which also matches `Type{A}`:

```julia
julia> struct Foo end

julia> foo(::Type{Union{Foo,B}}) where {B<:Base.IEEEFloat} = B
foo (generic function with 1 method)

julia> foo(Foo)
Union{}

```

So the solution seems to be to make sure there is also a method for

```julia
foo(::Type{Foo}) = Foo

```

which disambiguates that case. And then I think we are fine to use the unbounded form without issue

---

<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:** [June 26, 2025, 4:20pm UTC](https://discourse.julialang.org/t/weirdness-of-type-union-a-b-where-b-superb/130234/9 "2025-06-26T16:20:27Z")

</div>

Commented on the PR in question to show what I mean.
