When does julia give up on type inference?

f is a function that has quite a few methods and I use it somewhere deep in the call stack. I have two very similar methods, g, in one of them the return type of f is always inferred correctly for all x, and in the other one the return type of f is Any. Something like:

function g(x, y, z)
  fx = f(x) # inferred as ::Any
  h(fx, y, z)
end

function g(x, y)
  fx = f(x) # inferred correctly
  h(fx, y)
end

What are the criteria for julia to give up on type inference?

There’s no spec (it doesn’t affect program behavior), it’s implementation-defined:

https://github.com/JuliaLang/julia/blob/master/base/compiler/typeinfer.jl

If you want details on why this particular case isn’t getting type inferred, you’ll need to provide more context.

1 Like

Cthulhu.jl can be helpful in helping you debug these sorts of problems — you can @descend into the function and keep asking Julia for its code_warntype until you spot the issue.

3 Likes

I probably wouldn’t even have notice this, because it is not in a performance sensitive part of the code but the issue was that there were only h(fx::SomeAbstractType, ...) methods which caused the compiler to assume that h would never be called and the program crashed with “Reached the unreachable” errors.

This could easily be fixed by adding f(x)::SomeAbstractType but finding the issue caused me a lot of headache.

I finally found the issue with Cthulhu.jl, it saved me from madness. :octopus:

4 Likes

Reached the unreachable errors are always Julia bugs — not yours! If you can, please take the time to file a bug report so we can continue shaking these things out.

5 Likes

I wasn’t able to make a reproducible example, hence this topic.

This example has the same basic structure but is no failing:

abstract type TheAbstract end
struct ConcreteA <: TheAbstract end
struct ConcreteB <: TheAbstract end
struct ConcreteC <: TheAbstract end

abstract type TheOtherAbstract end
struct OtherA <: TheOtherAbstract end
struct OtherB <: TheOtherAbstract end
struct OtherC <: TheOtherAbstract end

f(x::ConcreteA) = OtherA()
f(x::ConcreteB) = OtherB()
f(x::ConcreteC) = OtherC()

run_type(::OtherA) = "run_otherA"
run_type(::OtherB) = "run_otherB"
run_type(::OtherC) = "run_otherC"

h(x::TheOtherAbstract, y::Float64, z::Float64) = println("h(x, y, z), ", typeof(x), " ", run_type(x))
h(x::TheOtherAbstract, y::Float64) = println("h(x, y), ", typeof(x), " ", run_type(x))

function g(x::TheAbstract, y, z)
    fx = f(x)
    h(fx, y , z)
end

function g(x::TheAbstract, y)
    fx = f(x)::Any # simulate failing type inference
    h(fx, y) # reached the unreachable error
end

g(ConcreteA(), 1.0, 1.0)
g(ConcreteA(), 1.0)
1 Like

I broke out all the necessary code into a repo and opened a github issue:

https://github.com/JuliaLang/julia/issues/32766

It seems fixed on Julia 1.3-alpha and master, but occurs on 1.0-1.2.