# What is the status of type inference of recursive calls? Odd statefulness?

**URL:** https://discourse.julialang.org/t/what-is-the-status-of-type-inference-of-recursive-calls-odd-statefulness/132545
**Category:** Internals & Design
**Tags:** recursion, type-inference
**Created:** [September 21, 2025, 9:40pm UTC](https://discourse.julialang.org/t/what-is-the-status-of-type-inference-of-recursive-calls-odd-statefulness/132545 "2025-09-21T21:40:15Z")
**Posts on this page:** 5
**Page:** 1

<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 21, 2025, 9:40pm UTC](https://discourse.julialang.org/t/what-is-the-status-of-type-inference-of-recursive-calls-odd-statefulness/132545/1 "2025-09-21T21:40:15Z")

</div>

Evidently, the situation has improved from v1.10.10 because the examples from the links below on v1.11.7 are inferred with concrete return types and pass `JET.@report_opt`. Note that type instabilities that don’t cause internal runtime dispatches pass `@report_opt` e.g. `f(x) = x[1]; @report_opt f(Any[1])`.

- Dec 2022 (unmerged?!) [RFC: Less aggressive recursion limiting by Keno · Pull Request #48059 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/48059)
- Jan 2024 [Failure to optimize due to type recursion - General Usage - Julia Programming Language](https://discourse.julialang.org/t/failure-to-optimize-due-to-type-recursion/108568)

But it’s still possible to run into problems. Here’s a [more complex MWE](https://discourse.julialang.org/t/identical-method-redefinition-suspiciously-optimizes-runtime-and-allocations/132156/21) rooted in nested `SVector`s discovered a couple weeks ago. I attempted to make a similar but more simplified example. Note that a simpler `sum` or `x->sum(x)` for the first input to the outer `sum` call results in type-stability on both v1.10.10 and v1.11.7.

```julia-auto
julia> using StaticArrays, BenchmarkTools, JET, Test

julia> inputs = (x->sum(identity, x), zero(SVector{4,SVector{2,Float64}}));

julia> @code_warntype sum(inputs...)
MethodInstance for sum(::var"#5#6", ::SVector{4, SVector{2, Float64}})
  from sum(f::Union{Function, Type}, a::StaticArray{<:Tuple, T}; dims, init) where T @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:295
Static Parameters
  T = SVector{2, Float64}
Arguments
  #self#::Core.Const(sum)
  f::Core.Const(var"#5#6"())
  a::SVector{4, SVector{2, Float64}}
Body::Any
1 ─ nothing
│ %2 = StaticArrays.:(var"#sum#204")::Core.Const(StaticArrays.var"#sum#204")
│ %3 = StaticArrays.:(:)::Core.Const(Colon())
│ %4 = StaticArrays._InitialValue()::Core.Const(StaticArrays._InitialValue())
│ %5 = (%2)(%3, %4, #self#, f, a)::Any
└── return %5

julia> Test.@inferred sum(inputs...)
ERROR: return type Float64 does not match inferred return type Any
Stacktrace:
 [1] error(s::String)
   @ Base .\error.jl:35
 [2] top-level scope
   @ REPL[16]:1

julia> @btime sum($(inputs[1]), $(inputs[2]))
  227.033 ns (5 allocations: 336 bytes)
0.0

```

And a different stateful twist: if we instantiate equivalent inputs but try `Test.@inferred` or a plain call first, then the compiler succeeds at recursive type inference. However, `@report_opt` does not.

```julia-auto
julia> using StaticArrays, BenchmarkTools, JET, Test # can stay in session or reload

julia> inputs = (x -> sum(identity, x), zero(SVector{4,SVector{2,Float64}}));

julia> Test.@inferred sum(inputs...) # or just execute sun(inputs...)
0.0

julia> @code_warntype sum(inputs...)
MethodInstance for sum(::var"#9#10", ::SVector{4, SVector{2, Float64}})
  from sum(f::Union{Function, Type}, a::StaticArray{<:Tuple, T}; dims, init) where T @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:295
Static Parameters
  T = SVector{2, Float64}
Arguments
  #self#::Core.Const(sum)
  f::Core.Const(var"#9#10"())
  a::SVector{4, SVector{2, Float64}}
Body::Float64
1 ─ nothing
│ %2 = StaticArrays.:(var"#sum#204")::Core.Const(StaticArrays.var"#sum#204")
│ %3 = StaticArrays.:(:)::Core.Const(Colon())
│ %4 = StaticArrays._InitialValue()::Core.Const(StaticArrays._InitialValue())
│ %5 = (%2)(%3, %4, #self#, f, a)::Float64
└── return %5

julia> @btime sum($(inputs[1]), $(inputs[2]))
  2.300 ns (0 allocations: 0 bytes)
0.0

julia> @report_opt sum(inputs...)
═════ 6 possible errors found ═════
┌ sum(f::var"#9#10", a::SVector{4, SVector{2, Float64}}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:295
│┌ sum(f::var"#9#10", a::SVector{4, SVector{2, Float64}}; dims::Colon, init::StaticArrays._InitialValue) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:295
││┌ _mapreduce(::var"#9#10", ::typeof(+), ::Colon, ::StaticArrays._InitialValue, ::Size{(4,)}, ::SVector{4, SVector{…}}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:153
│││┌ _mapfoldl(f::var"#9#10", op::typeof(+), dims::Colon, init::StaticArrays._InitialValue, ::Size{…}, a::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:180
││││┌ (::var"#9#10")(x::SVector{2, Float64}) @ Main ./REPL[21]:1
│││││┌ sum(f::typeof(identity), a::SVector{2, Float64}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:295
││││││┌ sum(f::typeof(identity), a::SVector{2, Float64}; dims::Colon, init::StaticArrays._InitialValue) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:295
│││││││┌ _mapreduce(::typeof(identity), ::typeof(+), ::Colon, ::StaticArrays._InitialValue, ::Size, ::SVector{2, Float64}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:153
││││││││ runtime dispatch detected: StaticArrays._mapfoldl(identity, +, Colon(), [quote]::StaticArrays._InitialValue, %1::Size, %2::SVector{2, Float64})::Any
│││││││└────────────────────
││││││┌ sum(f::typeof(identity), a::SVector{2, Float64}; dims::Colon, init::StaticArrays._InitialValue) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:295
│││││││ failed to optimize due to recursion: StaticArrays.var"#sum#204"(::Colon, ::StaticArrays._InitialValue, ::typeof(sum), ::typeof(identity), ::SVector{…})
││││││└────────────────────
│││││┌ sum(f::typeof(identity), a::SVector{2, Float64}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:295
││││││ failed to optimize due to recursion: sum(::typeof(identity), ::SVector{2, Float64})
│││││└────────────────────
││││┌ (::var"#9#10")(x::SVector{2, Float64}) @ Main ./REPL[21]:1
│││││ failed to optimize due to recursion: (::var"#9#10")(::SVector{2, Float64})
││││└────────────────────
│││┌ _mapfoldl(f::var"#9#10", op::typeof(+), dims::Colon, init::StaticArrays._InitialValue, ::Size{…}, a::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:155
││││ failed to optimize due to recursion: StaticArrays._mapfoldl(::var"#9#10", ::typeof(+), ::Colon, ::StaticArrays._InitialValue, ::Size{…}, ::SVector{…})
│││└────────────────────
││┌ _mapreduce(::var"#9#10", ::typeof(+), ::Colon, ::StaticArrays._InitialValue, ::Size{(4,)}, ::SVector{4, SVector{…}}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:153
│││ failed to optimize due to recursion: StaticArrays._mapreduce(::var"#9#10", ::typeof(+), ::Colon, ::StaticArrays._InitialValue, ::Size{…}, ::SVector{…})
││└────────────────────

```

What’s going on here and what could be done about it?

---

<div class="post-metadata">

### Author: ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)
#### Post date: [September 21, 2025, 11:44pm UTC](https://discourse.julialang.org/t/what-is-the-status-of-type-inference-of-recursive-calls-odd-statefulness/132545/2 "2025-09-21T23:44:26Z")

</div>

The analogous code for `Values` from StaticVectors.jl (which I believe is a fork if StaticArrays.jl) seems to work fine. Maybe this can help to understand what’s going on.

```julia-auto
julia> using StaticVectors: Values as SVector

julia> inputs = (x->sum(identity, x), zero(SVector{4,SVector{2,Float64}})); # NOTE: sum or x->sum(x) is stable

julia> @code_warntype sum(inputs...)
MethodInstance for sum(::var"#7#8", ::StaticVectors.Values{4, StaticVectors.Values{2, Float64}})
  from sum(f::Union{Function, Type}, a::StaticVectors.TupleVector{N, T}; dims) where {N, T} @ StaticVectors /usr/local/julia-depot/packages/StaticVectors/fpkZ5/src/mapreduce.jl:244
Static Parameters
  N = 4
  T = StaticVectors.Values{2, Float64}
Arguments
  #self#::Core.Const(sum)
  f::Core.Const(var"#7#8"())
  a::StaticVectors.Values{4, StaticVectors.Values{2, Float64}}
Body::Float64
1 ─ nothing
│ %2 = StaticVectors.:(var"#sum#108")::Core.Const(StaticVectors.var"#sum#108")
│ %3 = StaticVectors.:(:)::Core.Const(Colon())
│ %4 = (%2)(%3, #self#, f, a)::Float64
└── return %4

```

---

<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 26, 2025, 1:39am UTC](https://discourse.julialang.org/t/what-is-the-status-of-type-inference-of-recursive-calls-odd-statefulness/132545/3 "2025-09-26T01:39:37Z")

</div>

I tried comparing the printouts of `@descend`, and I really can’t spot a critical difference in method call chains or the source code. When type inference fails, the method signatures will lack type annotations and print “Warning: Inference terminated in an incomplete state due to argument-type changes during recursion”, but the signatures in the `Values` version do in fact change down the mild recursive chain. There’s just something in the recursion type inference heuristic I don’t know about. I’m not even sure if it’s related to the issue that changed between 1.10 and 1.11, which doesn’t mention the statefulness here.

---

<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 26, 2025, 12:22pm UTC](https://discourse.julialang.org/t/what-is-the-status-of-type-inference-of-recursive-calls-odd-statefulness/132545/4 "2025-09-26T12:22:20Z")

</div>

> [@Benny](#):
>
> comparing the printouts of `@descend`

In case you don’t know, the `remarks=true` option to `@descend` may give more info here. So: `@descend remarks=true ...`

---

<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 26, 2025, 7:09pm UTC](https://discourse.julialang.org/t/what-is-the-status-of-type-inference-of-recursive-calls-odd-statefulness/132545/5 "2025-09-26T19:09:15Z")

</div>

In this case, not significantly more info and I haven’t seen anything stand out.
