# Determining if UnionAll corresponds to a single type + Union{}

**URL:** <https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088>\
**Category:** Internals & Design\
**Tags:** type\
**Created:** [September 4, 2025, 6:42am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088 "2025-09-04T06:42:58Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![willtebbutt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/willtebbutt/32/6790_2.png) [@willtebbutt](https://discourse.julialang.org/u/willtebbutt)\
**Post date:** [September 4, 2025, 6:42am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/1 "2025-09-04T06:42:58Z")

</div>

A `UnionAll` such as `Type{T} where {T<:Tuple{typeof(sin),Float64}}` is a union containing a single type: `Type{Tuple{typeof(sin),Float64}}`

On the other hand, `Type{T} where {T<:Tuple{typeof(sin),Real}}` is a union over many types.

Is there a in way general to programmatically determine whether, for a given `UnionAll`, it contains only a single type?

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [September 4, 2025, 7:34am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/2 "2025-09-04T07:34:49Z")

</div>

Your first example contains two types because it also has the empty type `Union{}`.

So I guess you’re asking about a single **concrete** type?

Is this sufficient?

1. Check if all the bounds have a concrete upper bound.
2. Substitute these upper bounds in.
3. See if the resulting type is concrete.

Edit: step 1 should recurse because the upper bound can also be a union-all containing one concrete type.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [September 4, 2025, 8:50am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/3 "2025-09-04T08:50:24Z")

</div>

Shouldn’t this be the same as that UnionAll being a subtype of a given concrete type? I’m just not sure whether our subtyping handles this case.

---

<div class="post-metadata">

**Author:** ![willtebbutt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/willtebbutt/32/6790_2.png) [@willtebbutt](https://discourse.julialang.org/u/willtebbutt)\
**Post date:** [September 4, 2025, 9:17am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/4 "2025-09-04T09:17:27Z")

</div>

> Your first example contains two types because it also has the empty type `Union{}`.

Good point @cjdoris

> Is this sufficient?

Sorry, I’m not quite able to follow your steps here – would you be able to give an example?

> Shouldn’t this be the same as that UnionAll being a subtype of a given concrete type? I’m just not sure whether our subtyping handles this case.

@Sukera ooo interesting. The below doesn’t seem to give the answer we want, but I may have misunderstood you:

```julia
julia> sig = Tuple{typeof(sin),Float64}
Tuple{typeof(sin), Float64}

julia> Type{<:sig} <: Type{sig}
false

julia> Type{<:sig} <: Union{Type{sig},Type{Union{}}}
false

```

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [September 4, 2025, 10:50am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/5 "2025-09-04T10:50:15Z")

</div>

I think my earlier method was false because you can (almost?) always substitute `Union{}` for the type parameter and get a concrete type.

I think this means that (almost?) all `UnionAll`s have multiple concrete subtypes.

For example your example `Type{<:sig} <: Type{sig}` is false because ` Type{<:sig}` is equivalent to `Union{Type{sig},Type{Union{}}}`.

---

<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:** [September 4, 2025, 2:58pm UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/6 "2025-09-04T14:58:21Z")

</div>

`==` seems to work given the one type, though I don’t see this documented for iterated unions in particular.

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

```

> [@cjdoris](#):
>
> `Type{<:sig}` is equivalent to `Union{Type{sig},Type{Union{}}}`.

It’s not, one strictly subtypes the other:

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

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [September 5, 2025, 7:26am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/7 "2025-09-05T07:26:03Z")

</div>

> [@Benny](#):
>
> It’s not, one strictly subtypes the other

Indeed, I just meant that they have the same two subtypes, not that they are equal types.

---

<div class="post-metadata">

**Author:** ![willtebbutt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/willtebbutt/32/6790_2.png) [@willtebbutt](https://discourse.julialang.org/u/willtebbutt)\
**Post date:** [September 5, 2025, 8:33am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/8 "2025-09-05T08:33:10Z")

</div>

Thanks for your thoughts on this.

I wonder if the question is easier for us to answer if we restrict ourselves to a subset of possible `Tuple`s? 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?

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [September 5, 2025, 10:13am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/9 "2025-09-05T10:13:19Z")

</div>

Tuples are special because they are ~~invariant~~ covariant. In particular a tuple type is concrete if and only if all its parameters are concrete.

I think this means that a `<:Tuple` bound can only have one concrete tuple if and only if:

- it’s not `Vararg` (so the number of parameters is known); and
- each parameter bound only has one concrete subtype, so either:
  - it’s a concrete type, or
  - it’s bounded above by a concrete type, or
  - it’s bounded above by a type that only has one concrete subtype (and you can recurse on this condition if you like).

In practice you probably catch most cases without needing to recurse.

---

<div class="post-metadata">

**Author:** ![willtebbutt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/willtebbutt/32/6790_2.png) [@willtebbutt](https://discourse.julialang.org/u/willtebbutt)\
**Post date:** [September 5, 2025, 10:35am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/10 "2025-09-05T10:35:11Z")

</div>

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?

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

```

---

<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:** [September 5, 2025, 11:23am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/11 "2025-09-05T11:23:59Z")

</div>

I think your function is equivalent to just `isconcretetype`.

---

<div class="post-metadata">

**Author:** ![willtebbutt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/willtebbutt/32/6790_2.png) [@willtebbutt](https://discourse.julialang.org/u/willtebbutt)\
**Post date:** [September 5, 2025, 12:56pm UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/12 "2025-09-05T12:56:52Z")

</div>

Not quite:

```julia
julia> isconcretetype(Tuple{DataType})
true

julia> is_safe_to_optimise(Tuple{DataType})
false

```

I think (hope) you’re correct about every other concrete `Tuple` type though.

edit: note that this is an especially odd edge case:

```julia
julia> Tuple{Type{Float64}} <: Tuple{DataType}
true

```

despite the fact that `DataType` is concrete.

edit2: which I guess is true because

```julia
julia> Type{Float64} <: DataType
true

```

---

<div class="post-metadata">

**Author:** ![\_bernhard](https://avatars.discourse-cdn.com/v4/letter/_/bc79bd/32.png) [@\_bernhard](https://discourse.julialang.org/u/_bernhard)\
**Post date:** [September 5, 2025, 7:38pm UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/13 "2025-09-05T19:38:08Z")

</div>

> [@cjdoris](#):
>
> Tuples are special because they are invariant.

This seems to be a typo.

* * *

Invariant type parameters are in fact the default in julia

```julia-auto
julia> Int <: Integer
true

julia> Vector{Int} <: Vector{Integer}
false

```

and `Tuple` types are special because they are covariant.

```julia-auto
julia> Tuple{Int} <: Tuple{Integer}
true

```

* * *

[Julia Manual - parametric types](https://docs.julialang.org/en/v1/manual/types/#man-parametric-composite-types)  
[Julia Manual - Tuple types](https://docs.julialang.org/en/v1/manual/types/#Tuple-Types)

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [September 5, 2025, 7:50pm UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/14 "2025-09-05T19:50:47Z")

</div>

> [@\_bernhard](#):
>
> This seems to be a typo.

Thanks, corrected.

---

<div class="post-metadata">

**Author:** ![willtebbutt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/willtebbutt/32/6790_2.png) [@willtebbutt](https://discourse.julialang.org/u/willtebbutt)\
**Post date:** [September 10, 2025, 7:27am UTC](https://discourse.julialang.org/t/determining-if-unionall-corresponds-to-a-single-type-union/132088/15 "2025-09-10T07:27:29Z")

</div>

Thanks again everyone for your thoughts on this, I greatly appreciate you all taking the time to respond.

I don’t _think_ that we’ve landed on a completely satisfying solution (although I suspect that `is_safe_to_optimise` is fairly close), so I’m not going to give anything the green tick at this point, in case someone knows something we don’t and stumbles across this in the future!
