# Type-inference fails then succeeds for exact same function

**URL:** <https://discourse.julialang.org/t/type-inference-fails-then-succeeds-for-exact-same-function/105384>\
**Category:** General Usage\
**Tags:** componentarrays\
**Created:** [October 25, 2023, 3:36pm UTC](https://discourse.julialang.org/t/type-inference-fails-then-succeeds-for-exact-same-function/105384 "2023-10-25T15:36:34Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![Bizzi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bizzi/32/51484_2.png) [@Bizzi](https://discourse.julialang.org/u/Bizzi)\
**Post date:** [October 25, 2023, 3:36pm UTC](https://discourse.julialang.org/t/type-inference-fails-then-succeeds-for-exact-same-function/105384/1 "2023-10-25T15:36:34Z")

</div>

I’ve been facing multiple issues with type-inference, specially when using `ComponentArrays`. In particular, it may succeed or fail, apparently at random, for the same function, in the same program. What could be causing this? Does type-inference depend on any sort of state which could be changing throughout the script?

This is easiest to reproduce using Lux Chains of Custom Layers. Take the following example, where `DummyLayer` is just some wrapper for an actual NN layer:

```julia
using Lux, ComponentArrays, Random, Cthulhu

struct DummyLayer{T} <: Lux.AbstractExplicitLayer
    layer::T
end 

function (layer::DummyLayer)(X,ps,st) 
    fx, _ = layer.layer(X,ps,st)
    return fx, ()
end

Lux.initialparameters(rng::AbstractRNG,layer::DummyLayer) = Lux.initialparameters(rng::AbstractRNG,layer.layer)
Lux.initialstates(rng::AbstractRNG,layer::DummyLayer) = Lux.initialstates(rng::AbstractRNG,layer.layer)

n = 4
X = rand(n)
width = 12 

C = Chain(Dense(n=>width),Dense(width=>n))

D = DummyLayer(C)
DD = Chain(D,D)
ps, st = Lux.setup(Random.default_rng(),DD)
psc = ps |> ComponentArray

DD(X,psc,st)
@code_warntype DD(X,psc,st)

```

The last line outputs the following (note the `Tuple{Any,...}` in %2):

> **code\_warntype**
>
> ```julia
> MethodInstance for (::Chain{NamedTuple{(:layer_1, :layer_2), Tuple{DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}, DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}}}, Nothing})(::Vector{Float64}, ::ComponentVector{Float32, Vector{Float32}, Tuple{Axis{(layer_1 = ViewAxis(1:112, Axis(layer_1 = ViewAxis(1:60, Axis(weight = ViewAxis(1:48, ShapedAxis((12, 4), NamedTuple())), bias = ViewAxis(49:60, ShapedAxis((12, 1), NamedTuple())))), layer_2 = ViewAxis(61:112, Axis(weight = ViewAxis(1:48, ShapedAxis((4, 12), NamedTuple())), bias = ViewAxis(49:52, ShapedAxis((4, 1), NamedTuple())))))), layer_2 = ViewAxis(113:224, Axis(layer_1 = ViewAxis(1:60, Axis(weight = ViewAxis(1:48, ShapedAxis((12, 4), NamedTuple())), bias = ViewAxis(49:60, ShapedAxis((12, 1), NamedTuple())))), layer_2 = ViewAxis(61:112, Axis(weight = ViewAxis(1:48, ShapedAxis((4, 12), NamedTuple())), bias = ViewAxis(49:52, ShapedAxis((4, 1), NamedTuple())))))))}}}, ::NamedTuple{(:layer_1, :layer_2), Tuple{NamedTuple{(:layer_1, :layer_2), Tuple{NamedTuple{(), Tuple{}}, NamedTuple{(), Tuple{}}}}, NamedTuple{(:layer_1, :layer_2), Tuple{NamedTuple{(), Tuple{}}, NamedTuple{(), Tuple{}}}}}})
> from (c::Chain)(x, ps, st::NamedTuple) @ Lux C:\Users\55619\.julia\packages\Lux\3Kn7l\src\layers\containers.jl:478
> Arguments
> c::Chain{NamedTuple{(:layer_1, :layer_2), Tuple{DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}, DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}}}, Nothing} 
> x::Vector{Float64}
> ps::ComponentVector{Float32, Vector{Float32}, Tuple{Axis{(layer_1 = ViewAxis(1:112, Axis(layer_1 = ViewAxis(1:60, Axis(weight = ViewAxis(1:48, ShapedAxis((12, 4), NamedTuple())), bias = ViewAxis(49:60, ShapedAxis((12, 1), NamedTuple())))), layer_2 = ViewAxis(61:112, Axis(weight = ViewAxis(1:48, ShapedAxis((4, 12), NamedTuple())), bias = ViewAxis(49:52, ShapedAxis((4, 1), NamedTuple())))))), layer_2 = ViewAxis(113:224, Axis(layer_1 = ViewAxis(1:60, Axis(weight = ViewAxis(1:48, ShapedAxis((12, 4), NamedTuple())), bias = ViewAxis(49:60, ShapedAxis((12, 1), NamedTuple())))), layer_2 = ViewAxis(61:112, Axis(weight = ViewAxis(1:48, ShapedAxis((4, 12), NamedTuple())), bias = ViewAxis(49:52, ShapedAxis((4, 1), NamedTuple())))))))}}}
> st::Core.Const((layer_1 = (layer_1 = NamedTuple(), layer_2 = NamedTuple()), layer_2 = (layer_1 = NamedTuple(), layer_2 = NamedTuple())))
> Body::Tuple{Any, NamedTuple{(:layer_1, :layer_2), Tuple{Tuple{}, Tuple{}}}}
> 1 ─ %1 = Base.getproperty(c, :layers)::NamedTuple{(:layer_1, :layer_2), Tuple{DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}, DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}}}
> │ %2 = Lux.applychain(%1, x, ps, st)::Tuple{Any, NamedTuple{(:layer_1, :layer_2), Tuple{Tuple{}, Tuple{}}}}
> └── return %2
> 
> ```

However, if I now _redeclare the exact same function_ in the subsequent lines, the compiler is able to infer all the types and the call becomes type-stable (!):

```julia
function (layer::DummyLayer)(X,ps,st) 
    fx, _ = layer.layer(X,ps,st)
    return fx, ()
end

DD(X,psc,st)
@code_warntype DD(X,psc,st)

```

> **code\_warntype**
>
> ```julia
> MethodInstance for (::Chain{NamedTuple{(:layer_1, :layer_2), Tuple{DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}, DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}}}, Nothing})(::Vector{Float64}, ::ComponentVector{Float32, Vector{Float32}, Tuple{Axis{(layer_1 = ViewAxis(1:112, Axis(layer_1 = ViewAxis(1:60, Axis(weight = ViewAxis(1:48, ShapedAxis((12, 4), NamedTuple())), bias = ViewAxis(49:60, ShapedAxis((12, 1), NamedTuple())))), layer_2 = ViewAxis(61:112, Axis(weight = ViewAxis(1:48, ShapedAxis((4, 12), NamedTuple())), bias = ViewAxis(49:52, ShapedAxis((4, 1), NamedTuple())))))), layer_2 = ViewAxis(113:224, Axis(layer_1 = ViewAxis(1:60, Axis(weight = ViewAxis(1:48, ShapedAxis((12, 4), NamedTuple())), bias = ViewAxis(49:60, ShapedAxis((12, 1), NamedTuple())))), layer_2 = ViewAxis(61:112, Axis(weight = ViewAxis(1:48, ShapedAxis((4, 12), NamedTuple())), bias = ViewAxis(49:52, ShapedAxis((4, 1), NamedTuple())))))))}}}, ::NamedTuple{(:layer_1, :layer_2), Tuple{NamedTuple{(:layer_1, :layer_2), Tuple{NamedTuple{(), Tuple{}}, NamedTuple{(), Tuple{}}}}, NamedTuple{(:layer_1, :layer_2), Tuple{NamedTuple{(), Tuple{}}, NamedTuple{(), Tuple{}}}}}})
> from (c::Chain)(x, ps, st::NamedTuple) @ Lux C:\Users\55619\.julia\packages\Lux\3Kn7l\src\layers\containers.jl:478
> Arguments
> c::Chain{NamedTuple{(:layer_1, :layer_2), Tuple{DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}, DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}}}, Nothing} 
> x::Vector{Float64}
> ps::ComponentVector{Float32, Vector{Float32}, Tuple{Axis{(layer_1 = ViewAxis(1:112, Axis(layer_1 = ViewAxis(1:60, Axis(weight = ViewAxis(1:48, ShapedAxis((12, 4), NamedTuple())), bias = ViewAxis(49:60, ShapedAxis((12, 1), NamedTuple())))), layer_2 = ViewAxis(61:112, Axis(weight = ViewAxis(1:48, ShapedAxis((4, 12), NamedTuple())), bias = ViewAxis(49:52, ShapedAxis((4, 1), NamedTuple())))))), layer_2 = ViewAxis(113:224, Axis(layer_1 = ViewAxis(1:60, Axis(weight = ViewAxis(1:48, ShapedAxis((12, 4), NamedTuple())), bias = ViewAxis(49:60, ShapedAxis((12, 1), NamedTuple())))), layer_2 = ViewAxis(61:112, Axis(weight = ViewAxis(1:48, ShapedAxis((4, 12), NamedTuple())), bias = ViewAxis(49:52, ShapedAxis((4, 1), NamedTuple())))))))}}}
> st::Core.Const((layer_1 = (layer_1 = NamedTuple(), layer_2 = NamedTuple()), layer_2 = (layer_1 = NamedTuple(), layer_2 = NamedTuple())))
> Body::Tuple{Vector{Float64}, NamedTuple{(:layer_1, :layer_2), Tuple{Tuple{}, Tuple{}}}}
> 1 ─ %1 = Base.getproperty(c, :layers)::NamedTuple{(:layer_1, :layer_2), Tuple{DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}, DummyLayer{Chain{NamedTuple{(:layer_1, :layer_2), Tuple{Dense{true, typeof(tanh_fast), typeof(glorot_uniform), typeof(zeros32)}, Dense{true, typeof(identity), typeof(glorot_uniform), typeof(zeros32)}}}, Nothing}}}}
> │ %2 = Lux.applychain(%1, x, ps, st)::Tuple{Vector{Float64}, NamedTuple{(:layer_1, :layer_2), Tuple{Tuple{}, Tuple{}}}}
> └── return %2
> 
> ```

(Julia 1.9, VSCode, Windows)

Why should inference work for the second time but not the first? Did I perhaps give the compiler more information by redeclaring the function after running it?

Likewise, is it possible to bypass inference when it stops working? This has been a huge source of headaches; it would be nice to be able to just _tell_ the compiler what the output of a function is going to be.

---

<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:** [October 25, 2023, 4:41pm UTC](https://discourse.julialang.org/t/type-inference-fails-then-succeeds-for-exact-same-function/105384/2 "2023-10-25T16:41:14Z")

</div>

I think this is a known issue, these two Github issues seem like they could be relevant:

> <https://github.com/JuliaLang/julia/issues/35800>
>
> Using Julia master (newer than 301db971daaeeb627ba768375538e6e7ff36d215), the fo…llowing does not infer the correct type (see also the discussion in #34048):
> \`\`\`julia
> using Test, LinearAlgebra
> \# @inferred mapreduce(norm, +, \[rand(1)\]);
> @inferred mapreduce(norm, +, \[rand(1)\]; init = 0.);
> \`\`\`
> Note that the third line works if you comment out the second line (start from a fresh Julia session), which makes testing this issue difficult.

> <https://github.com/JuliaLang/julia/issues/50735>
>
> MWE, here's the setup code isolated to just ForwardDiff:
> 
> \`\`\`julia
> using Forw…ardDiff
> const DUALCHECK\_RECURSION\_MAX = 10
> 
> """
> reduce\_tup(f::F, inds::Tuple{Vararg{Any,N}}) where {F,N}
> 
> An optimized \`reduce\` for tuples. \`Base.reduce\`'s \`afoldl\` will often not inline.
> Additionally, \`reduce\_tup\` attempts to order the reduction in an optimal manner.
> 
> More importantly, \`reduce\_tup(\_pick\_range, inds)\` often performs better than \`reduce(\_pick\_range, inds)\`.
> """
> @generated function reduce\_tup(f::F, inds::Tuple{Vararg{Any, N}}) where {F, N}
> q = Expr(:block, Expr(:meta, :inline, :propagate\_inbounds))
> if N == 1
> push!(q.args, :(inds\[1\]))
> return q
> end
> syms = Vector{Symbol}(undef, N)
> i = 0
> for n in 1:N
> syms\[n\] = iₙ = Symbol(:i\_, (i += 1))
> push!(q.args, Expr(:(=), iₙ, Expr(:ref, :inds, n)))
> end
> W = 1 \<\< (8sizeof(N) - 2 - leading\_zeros(N))
> while W \> 0
> \_N = length(syms)
> for \_ in (2W):W:\_N
> for w in 1:W
> new\_sym = Symbol(:i\_, (i += 1))
> push!(q.args, Expr(:(=), new\_sym, Expr(:call, :f, syms\[w\], syms\[w + W\])))
> syms\[w\] = new\_sym
> end
> deleteat!(syms, (1 + W):(2W))
> end
> W \>\>\>= 1
> end
> q
> end
> 
> """
> promote\_dual(::Type{T},::Type{T2})
> 
> 
> Is like the number promotion system, but always prefers a dual number type above
> anything else. For higher order differentiation, it returns the most dualiest of
> them all. This is then used to promote \`u0\` into the suspected highest differentiation
> space for solving the equation.
> """
> promote\_dual(::Type{T}, ::Type{T2}) where {T, T2} = T
> promote\_dual(::Type{T}, ::Type{T2}) where {T \<: ForwardDiff.Dual, T2} = T
> function promote\_dual(::Type{T},
> ::Type{T2}) where {T \<: ForwardDiff.Dual, T2 \<: ForwardDiff.Dual}
> T
> end
> promote\_dual(::Type{T}, ::Type{T2}) where {T, T2 \<: ForwardDiff.Dual} = T2
> 
> function promote\_dual(::Type{T},
> ::Type{T2}) where {T3, T4, V, V2 \<: ForwardDiff.Dual, N, N2,
> T \<: ForwardDiff.Dual{T3, V, N},
> T2 \<: ForwardDiff.Dual{T4, V2, N2}}
> T2
> end
> function promote\_dual(::Type{T},
> ::Type{T2}) where {T3, T4, V \<: ForwardDiff.Dual, V2, N, N2,
> T \<: ForwardDiff.Dual{T3, V, N},
> T2 \<: ForwardDiff.Dual{T4, V2, N2}}
> T
> end
> function promote\_dual(::Type{T},
> ::Type{T2}) where {
> T3, V \<: ForwardDiff.Dual, V2 \<: ForwardDiff.Dual,
> N,
> T \<: ForwardDiff.Dual{T3, V, N},
> T2 \<: ForwardDiff.Dual{T3, V2, N}}
> ForwardDiff.Dual{T3, promote\_dual(V, V2), N}
> end
> 
> \# \`reduce\` and \`map\` are specialized on tuples to be unrolled (via recursion)
> \# Therefore, they can be type stable even with heterogeneous input types.
> \# We also don't care about allocating any temporaries with them, as it should
> \# all be unrolled and optimized away.
> \# Being unrolled also means const prop can work for things like
> \# \`mapreduce(f, op, propertynames(x))\`
> \# where \`f\` may call \`getproperty\` and thus have return type dependent
> \# on the particular symbol.
> \# \`mapreduce\` hasn't received any such specialization.
> @inline diffeqmapreduce(f::F, op::OP, x::Tuple) where {F, OP} = reduce\_tup(op, map(f, x))
> @inline function diffeqmapreduce(f::F, op::OP, x::NamedTuple) where {F, OP}
> reduce\_tup(op, map(f, x))
> end
> \# For other container types, we probably just want to call \`mapreduce\`
> @inline diffeqmapreduce(f::F, op::OP, x) where {F, OP} = mapreduce(f, op, x)
> 
> """
> anyeltypedual(x)
> 
> 
> Searches through a type to see if any of its values are parameters. This is used to
> then promote other values to match the dual type. For example, if a user passes a parameter
> 
> which is a \`Dual\` and a \`u0\` which is a \`Float64\`, after the first time step, \`f(u,p,t) = p\*u\`
> will change \`u0\` from \`Float64\` to \`Dual\`. Thus the state variable always needs to be converted
> to a dual number before the solve. Worse still, this needs to be done in the case of
> \`f(du,u,p,t) = du\[1\] = p\*u\[1\]\`, and thus running \`f\` and taking the return value is not a valid
> way to calculate the required state type.
> 
> But given the properties of automatic differentiation requiring that differentiation of parameters
> implies differentiation of state, we assume any dual parameters implies differentiation of state
> and then attempt to upconvert \`u0\` to match that dual-ness. Because this changes types, this needs
> to be specified at compiled time and thus cannot have a Bool-based opt out, so in the future this
> may be extended to use a preference system to opt-out with a \`UPCONVERT\_DUALS\`. In the case where
> upconversion is not done automatically, the user is required to upconvert all initial conditions
> themselves, for an example of how this can be confusing to a user see
> https://discourse.julialang.org/t/typeerror-in-julia-turing-when-sampling-for-a-forced-differential-equation/82937
> """
> function anyeltypedual(x, counter = 0)
> if propertynames(x) === ()
> Any
> elseif counter \< DUALCHECK\_RECURSION\_MAX
> diffeqmapreduce(DualEltypeChecker(x, counter), promote\_dual,
> map(Val, propertynames(x)))
> else
> Any
> end
> end
> 
> \# Opt out since these are using for preallocation, not differentiation
> anyeltypedual(x::Union{ForwardDiff.AbstractConfig, Module}, counter = 0) = Any
> anyeltypedual(x::Type{T}, counter = 0) where {T \<: ForwardDiff.AbstractConfig} = Any
> 
> Base.@pure function \_\_anyeltypedual(::Type{T}) where {T}
> hasproperty(T, :parameters) ?
> mapreduce(anyeltypedual, promote\_dual, T.parameters; init = Any) : T
> end
> anyeltypedual(::Type{T}, counter = 0) where {T} = \_\_anyeltypedual(T)
> anyeltypedual(::Type{T}, counter = 0) where {T \<: ForwardDiff.Dual} = T
> function anyeltypedual(::Type{T}, counter = 0) where {T \<: Union{AbstractArray, Set}}
> anyeltypedual(eltype(T))
> end
> Base.@pure function \_\_anyeltypedual\_ntuple(::Type{T}) where {T \<: NTuple}
> if isconcretetype(eltype(T))
> return eltype(T)
> end
> if isempty(T.parameters)
> Any
> else
> mapreduce(anyeltypedual, promote\_dual, T.parameters; init = Any)
> end
> end
> anyeltypedual(::Type{T}, counter = 0) where {T \<: NTuple} = \_\_anyeltypedual\_ntuple(T)
> 
> \# Any in this context just means not Dual
> anyeltypedual(x::Number, counter = 0) = anyeltypedual(typeof(x))
> anyeltypedual(x::Union{String, Symbol}, counter = 0) = typeof(x)
> function anyeltypedual(x::Union{Array{T}, AbstractArray{T}, Set{T}},
> counter = 0) where {
> T \<:
> Union{Number,
> Symbol,
> String}}
> anyeltypedual(T)
> end
> function anyeltypedual(x::Union{Array{T}, AbstractArray{T}, Set{T}},
> counter = 0) where {
> T \<: Union{
> AbstractArray{
> \<:Number,
> },
> Set{
> \<:Number,
> }}}
> anyeltypedual(eltype(x))
> end
> function anyeltypedual(x::Union{Array{T}, AbstractArray{T}, Set{T}},
> counter = 0) where {N, T \<: NTuple{N, \<:Number}}
> anyeltypedual(eltype(x))
> end
> 
> \# Try to avoid this dispatch because it can lead to type inference issues when !isconcrete(eltype(x))
> function anyeltypedual(x::AbstractArray, counter = 0)
> if isconcretetype(eltype(x))
> anyeltypedual(eltype(x))
> elseif !isempty(x) && all(i -\> isassigned(x, i), 1:length(x)) &&
> counter \< DUALCHECK\_RECURSION\_MAX
> counter += 1
> mapreduce(y -\> anyeltypedual(y, counter), promote\_dual, x)
> else
> # This fallback to Any is required since otherwise we cannot handle \`undef\` in all cases
> # misses cases of
> Any
> end
> end
> 
> function anyeltypedual(x::Set, counter = 0)
> if isconcretetype(eltype(x))
> anyeltypedual(eltype(x))
> else
> # This fallback to Any is required since otherwise we cannot handle \`undef\` in all cases
> Any
> end
> end
> 
> function anyeltypedual(x::Tuple, counter = 0)
> # Handle the empty tuple case separately for inference and to avoid mapreduce error
> if x === ()
> Any
> else
> diffeqmapreduce(anyeltypedual, promote\_dual, x)
> end
> end
> function anyeltypedual(x::Dict, counter = 0)
> isempty(x) ? eltype(values(x)) : mapreduce(anyeltypedual, promote\_dual, values(x))
> end
> function anyeltypedual(x::NamedTuple, counter = 0)
> isempty(x) ? Any : diffeqmapreduce(anyeltypedual, promote\_dual, values(x))
> end
> @inline function promote\_u0(u0, p, t0)
> if !(eltype(u0) \<: ForwardDiff.Dual)
> T = anyeltypedual(p)
> T === Any && return u0
> if T \<: ForwardDiff.Dual
> return T.(u0)
> end
> end
> u0
> end
> 
> @inline function promote\_u0(u0::AbstractArray{\<:Complex}, p, t0)
> if !(real(eltype(u0)) \<: ForwardDiff.Dual)
> T = anyeltypedual(p)
> T === Any && return u0
> if T \<: ForwardDiff.Dual
> Ts = promote\_type(T, eltype(u0))
> return Ts.(u0)
> end
> end
> u0
> end
> 
> struct DualEltypeChecker{T}
> x::T
> counter::Int
> DualEltypeChecker(x::T, counter::Int) where {T} = new{T}(x, counter + 1)
> end
> 
> function (dec::DualEltypeChecker)(::Val{Y}) where {Y}
> isdefined(dec.x, Y) || return Any
> dec.counter \>= DUALCHECK\_RECURSION\_MAX && return Any
> anyeltypedual(getproperty(dec.x, Y), dec.counter)
> end
> 
> \# use \`getfield\` on \`Pairs\`, see https://github.com/JuliaLang/julia/pull/39448
> function (dec::DualEltypeChecker{\<:Base.Pairs})(::Val{Y}) where {Y}
> isdefined(dec.x, Y) || return Any
> dec.counter \>= DUALCHECK\_RECURSION\_MAX && return Any
> anyeltypedual(getfield(dec.x, Y), dec.counter)
> end
> 
> struct Thing
> a::Float64
> end
> struct Wrapper1{T}
> thing::T
> end
> struct Wrapper2{T}
> thing::T
> end
> 
> thing = Thing(1.0)
> x = 1.0
> \`\`\`
> 
> Now the checks:
> 
> \`\`\`julia
> promote\_u0(x, Wrapper1(thing), (0.0, 1.0))
> @code\_warntype promote\_u0(x, Wrapper1(thing), (0.0, 1.0))
> \`\`\`
> 
> \`\`\`
> MethodInstance for promote\_u0(::Float64, ::Wrapper1{Thing}, ::Tuple{Float64, Float64})
> from promote\_u0(u0, p, t0) @ Main c:\\Users\\accou\\OneDrive\\Computer\\Desktop\\test.jl:346
> Arguments
> #self#::Core.Const(promote\_u0)
> u0::Float64
> p::Wrapper1{Thing}
> t0::Tuple{Float64, Float64}
> Locals
> T::Any
> Body::Any
> 1 ─ nothing
> │ Core.NewvarNode(:(T))
> │ %3 = Main.eltype(u0)::Core.Const(Float64)
> │ %4 = ForwardDiff.Dual::Core.Const(ForwardDiff.Dual)
> │ %5 = (%3 \<: %4)::Core.Const(false)
> │ %6 = !%5::Core.Const(true)
> └── goto #6 if not %6
> 2 ─ (T = Main.anyeltypedual(p))
> │ %9 = (T === Main.Any)::Bool
> └── goto #4 if not %9
> 3 ─ return u0
> 4 ─ %12 = T::Any
> │ %13 = ForwardDiff.Dual::Core.Const(ForwardDiff.Dual)
> │ %14 = (%12 \<: %13)::Bool
> └── goto #6 if not %14
> 5 ─ %16 = Base.broadcasted(T, u0)::Base.Broadcast.Broadcasted{Style, Nothing} where Style\<:Union{Nothing, Base.Broadcast.BroadcastStyle}
> │ %17 = Base.materialize(%16)::Any
> └── return %17
> 6 ┄ return u0
> \`\`\`
> 
> \`\`\`julia
> promote\_u0(x, Wrapper2(thing), (0.0, 1.0))
> @code\_warntype promote\_u0(x, Wrapper2(thing), (0.0, 1.0))
> \`\`\`
> 
> \`\`\`
> MethodInstance for promote\_u0(::Float64, ::Wrapper2{Thing}, ::Tuple{Float64, Float64})
> from promote\_u0(u0, p, t0) @ Main c:\\Users\\accou\\OneDrive\\Computer\\Desktop\\test.jl:346
> Arguments
> #self#::Core.Const(promote\_u0)
> u0::Float64
> p::Wrapper2{Thing}
> t0::Tuple{Float64, Float64}
> Locals
> T::Type{Any}
> Body::Float64
> 1 ─ nothing
> │ Core.NewvarNode(:(T))
> │ %3 = Main.eltype(u0)::Core.Const(Float64)
> │ %4 = ForwardDiff.Dual::Core.Const(ForwardDiff.Dual)
> │ %5 = (%3 \<: %4)::Core.Const(false)
> │ %6 = !%5::Core.Const(true)
> └── goto #5 if not %6
> 2 ─ (T = Main.anyeltypedual(p))
> │ %9 = (T::Core.Const(Any) === Main.Any)::Core.Const(true)
> └── goto #4 if not %9
> 3 ─ return u0
> 4 ─ Core.Const(:(T))
> │ Core.Const(:(ForwardDiff.Dual))
> │ Core.Const(:(%12 \<: %13))
> │ Core.Const(:(goto %19 if not %14))
> │ Core.Const(:(Base.broadcasted(T, u0)))
> │ Core.Const(:(Base.materialize(%16)))
> └── Core.Const(:(return %17))
> 5 ┄ Core.Const(:(return u0))
> \`\`\`
> 
> First reported as https://github.com/SciML/DiffEqBase.jl/issues/918

---

<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:** [October 25, 2023, 5:19pm UTC](https://discourse.julialang.org/t/type-inference-fails-then-succeeds-for-exact-same-function/105384/3 "2023-10-25T17:19:41Z")

</div>

> [@Bizzi](#):
>
> is it possible to bypass inference when it stops working? This has been a huge source of headaches; it would be nice to be able to just _tell_ the compiler what the output of a function is going to be.

Type assertions are this, when applied to the return value of the function. For example; do `return expr::T` instead of `return expr`.
