# Understanding dispatch through TypeVar

**URL:** https://discourse.julialang.org/t/understanding-dispatch-through-typevar/131091
**Category:** General Usage
**Tags:** question, type, parametric-types
**Created:** [July 27, 2025, 11:32am UTC](https://discourse.julialang.org/t/understanding-dispatch-through-typevar/131091 "2025-07-27T11:32:55Z")
**Posts on this page:** 5
**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: [July 27, 2025, 11:32am UTC](https://discourse.julialang.org/t/understanding-dispatch-through-typevar/131091/1 "2025-07-27T11:32:55Z")

</div>

I’m trying to understand how dispatch happens through TypeVars related to some debugging of Mooncake.jl internals. For example:

```julia-repl
julia> foo(::Type{<:Array{T,N}}) where {T,N} = N
foo (generic function with 1 method)

julia> foo(Array{Float64,N}) where {N}
ERROR: UndefVarError: `N` not defined in static parameter matching
Suggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.
Stacktrace:
 [1] foo(::Type{Array{Float64, N}})
   @ Main ./REPL[1]:1
 [2] top-level scope
   @ REPL[2]:1

```

I’m confused why this matches the signature even though `N` is not concrete. The method does not have anything inherently wrong with it despite the error’s suggestion.

Another example:

```julia-repl
julia> bar(::Type{<:Array{T,N}}) where {T,N} = T
bar (generic function with 1 method)

julia> bar(::Type{<:Array{T}}) where {T} = 1
bar (generic function with 2 methods)

julia> bar(Array{Float64,N}) where {N}
Float64

```

Shouldn’t it match the second method, not the first?

I also don’t fully understand where these types like `(Array{Float64,N} where {N}).body` appear in the wild. But Mooncake.jl is set up to handle them. So maybe there are certain cases of inference where these do appear?

---

<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: [July 27, 2025, 7:47pm UTC](https://discourse.julialang.org/t/understanding-dispatch-through-typevar/131091/2 "2025-07-27T19:47:34Z")

</div>

> [@MilesCranmer](#):
>
> I’m confused why this matches the signature

I don’t know the answer here, however I’ll reference a relevant Github issue 😉

- [Signature matching for unbound type parameters · Issue #58899 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/58899)

> [@MilesCranmer](#):
>
> why this matches the signature even though `N` is not concrete

Just to clarify one thing: whether the type parameter is concrete is certainly not relevant. Parameter matching succeeds when the lower bound on the parameter equals the upper bound on the parameter, which is not the case here where the default bounds (bottom and top types) obviously don’t equal each other.

> [@MilesCranmer](#):
>
> The method does not have anything inherently wrong with it despite the error’s suggestion.

Well, you could have used `@isdefined N` in the method body. Does that help?

> [@MilesCranmer](#):
>
> ```julia-auto
> julia> foo(Array{Float64,N}) where {N}
> 
> ```

First time I see this syntax! I wonder whether it is supported, or just an accidentally leaked implementation detail of the compiler frontend?

NB: you probably know this, but doing `foo(Array{Float64})` seems to behave as you expect.

> [@MilesCranmer](#):
>
> Shouldn’t it match the second method, not the first?

The second method is less specific due to subtyping:

```julia-repl
julia> m1 = Tuple{Type{A} where {A <: Array{T, N}}} where {T, N}
Tuple{Type{A} where A<:Array{T, N}} where {T, N}

julia> m2 = Tuple{Type{A} where {A <: Array{T}}} where {T}
Tuple{Type{A} where A<:(Array{T})} where T

julia> m1 <: m2
true

julia> m2 <: m1
false

```

---

<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: [July 27, 2025, 8:37pm UTC](https://discourse.julialang.org/t/understanding-dispatch-through-typevar/131091/3 "2025-07-27T20:37:19Z")

</div>

Thanks. Yeah my GH issue perhaps simplifies the weirdness a bit:

```julia-repl
julia> foo(::T) where {T} = T;

julia> foo(::Type{T}) where {T} = Type{T};

julia> foo(Type{T} where {T})
Type{Type}

julia> foo(Type{T}) where {T}
ERROR: UndefVarError: `T` not defined in static parameter matching
#= ... =#

#= but, the following works fine =#

julia> bar(::T) where {T} = T;

julia> bar(Type{T}) where {T}
DataType

```

> [@nsajko](#):
>
> Just to clarify one thing: whether the type parameter is concrete is certainly not relevant. Parameter matching succeeds when the lower bound on the parameter equals the upper bound on the parameter, which is not the case here where the default bounds (bottom and top types) obviously don’t equal each other.

Ok. It kind of makes sense to me. So the method

```julia-repl
julia> foo(::Type{T}) where {T} = T

```

matches `(Type{T}) where {T}` but _not_ `(Type{T} where {T})`.

It just feels weird you can match on “parametric type that isn’t yet concretely defined” but not on a UnionAll - which, in my mind, looks like the same thing.

---

<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: [July 27, 2025, 8:46pm UTC](https://discourse.julialang.org/t/understanding-dispatch-through-typevar/131091/4 "2025-07-27T20:46:37Z")

</div>

P.S., in case you have any insights, this came up in a discussion here: [Failure of traversing through nested structures · Issue #661 · chalk-lab/Mooncake.jl · GitHub](https://github.com/chalk-lab/Mooncake.jl/issues/661#issuecomment-3124696434).

Basically, we were getting an “Unreachable reached” error, and @yebai found a fix to Mooncake.jl that literally just did

```diff
@generated function _build_fdata_cartesian(
    ::Type{P}, x::Tuple, fdata::Tuple{Vararg{Any,N}}, ::Val{nfield}, ::Val{names}
) where {P,N,nfield,names}
+ 2*N
    quote
        processed_fdata = Base.Cartesian.@ntuple(

```

in the body, and it fixed things… (We haven’t found a MWE for Julia yet though)

---

<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: [July 27, 2025, 8:53pm UTC](https://discourse.julialang.org/t/understanding-dispatch-through-typevar/131091/5 "2025-07-27T20:53:44Z")

</div>

> [@MilesCranmer](#):
>
> I’m confused why this matches the signature even though `N` is not concrete.

Let’s look at what the call lowers to in v1.11.6:

```julia-auto
julia> Meta.@lower foo(Array{Float64,N}) where {N}
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = Core.TypeVar(:N)
│ N = %1
│ %3 = N
│ %4 = foo
│ %5 = N
│ %6 = Core.apply_type(Array, Float64, %5)
│ %7 = (%4)(%6)
│ %8 = Core.UnionAll(%3, %7)
└── return %8
))))

```

Cleaning that up a bit, it’s

```julia-auto
let n = TypeVar(:N); UnionAll(n, foo(Core.apply_type(Array, Float64, n))) end

```

The overall expression is a `UnionAll` instantiation with a `foo` call in the leading section. Let’s look at its input:

```julia-auto
julia> Core.apply_type(Array, Float64, TypeVar(:N))
Array{Float64, N}

julia> Core.apply_type(Array, Float64, TypeVar(:N)) |> typeof
DataType

julia> (Array{Float64, N} where N).body |> typeof
DataType

```

It looks like a `UnionAll` but it’s actually an internal component `DataType`. It dispatches to the method like any other `DataType`, though `foo` throws an error because `TypeVar(:N)` doesn’t provide an actual value required for the method parameter `N` used in the body (`bar` doesn’t use it in the body so it got a pass). The `.body` property tends to appear in internal type computation, like `Base.unwrap_unionall`. Presumably the real world version of `foo` alters the `DataType` component before instantiating the `UnionAll`.
