Compiler/Optimizer Type Inference Bug with Recursive, Nested Types (in v1.12.6)

The following two examples each produce supposedly impossible results or crash the repl entirely, even though the code is pure Julia. I did not manage to reduce the examples further, at least not by simplifying single lines:

For instance, in the first example, at line 17, changing Vector{Tuple{T,T}} to just Vector{T} fixes the output. In the second example, removing the first field x::UInt from Between so far leads to correct results.

Maybe I am still overlooking something, but as far as I can tell, there is nothing wrong with the code itself.

First example:

mutable struct Bottom{Val_T}
    const val::Val_T
    Bottom{Val_T}() where Val_T = new{Val_T}(Val_T())
end

struct Mid{Val_T}
    val::Val_T
    bottom::Bottom{Val_T}
end

function Mid{Val_T}() where Val_T
    bottom = Bottom{Val_T}()
    Mid(bottom.val, bottom)
end

mutable struct Val{T}
    tuple_TT::Vector{Tuple{T,T}}
    Val{T}() where T = new{T}()
end

struct TopLevel
    mid::Union{Nothing,Mid{Val{TopLevel}}}
end

TopLevel() = TopLevel(Mid{Val{TopLevel}}())
foo(x::Mid) = x

function test()
    x = TopLevel()
    v = TopLevel[x, x]
    y = v[1]

    println(typeof(y) === typeof(x)) # true

    x_mid = getfield(x, :mid)
    println(typeof(x_mid)) # Mid{Val{TopLevel}}
    foo(x_mid)

    y_mid = getfield(y, :mid)
    println(typeof(y_mid)) # TopLevel
    try
        foo(y_mid)
    catch err
        println(err) # MethodError(Main.foo, (TopLevel(Mid{Val{TopLevel}}(Val{TopLevel}(#undef), Bottom{Val{TopLevel}}(Val{TopLevel}(#undef)))),), h*) (*for some UInt h)
    end
end
test()

Second example:

struct Between{Val_T}
    x::UInt
    y::Val_T
    Between{Val_T}() where Val_T = new{Val_T}(UInt(0), Val_T())
end

mutable struct Bottom{Val_T} 
    const val::Val_T
    Bottom{Val_T}() where Val_T = new{Val_T}(Val_T()) 
end

struct Mid{Val_T} 
    val::Val_T
    bottom::Bottom{Val_T}
end

function Mid{Val_T}() where Val_T
    bottom = Bottom{Val_T}()
    Mid(bottom.val, bottom)
end

mutable struct Val{T}
    tuple_TT::Vector{Tuple{T,T}}
    Val{T}() where T = new{T}()
end

struct TopLevel
    mid::Mid{Between{Val{TopLevel}}}
end

TopLevel() = TopLevel(Mid{Between{Val{TopLevel}}}())

function test()
    x = TopLevel()
    v = [x, x]
    y = v[1]

    println("types start equal: $(typeof(y) === typeof(x)) \n") 
    println(y.mid.bottom) # prints ReentrantLock()

    x = TopLevel()
    v = Any[x, x]
    y = v[1]

    println("types start equal: $(typeof(y) === typeof(x)) \n") 
    # println(y.mid) # when uncommented, the repl crashes seemingly randomly sometimes directly, sometimes after rerunning the script several times
end
test()

This seems a bug report you should send on the Julia issue tracker here: Issues · JuliaLang/julia · GitHub

I suspect this is a duplicate of Partially initialized datatypes are prone to crashes · Issue #60919 · JuliaLang/julia · GitHub, which was fixed (very) recently by Use typegroup mechanism for ordinary structs - Pull Request #61306 - JuliaLang/julia - GitHub, and will land in 1.14

Whether duplicate or not, it seems to me that the typegroup mechanic in v1.14 is going to change handling of recursive types fundamentally, such that it also covers these cases.

This is pretty odd, does 1.14 fix this bit? Even if fixed by then, it could be worth its own bug report for potentially earlier patches.

julia>     x = TopLevel()
TopLevel(Mid{Val{TopLevel}}(Val{TopLevel}(#undef), Bottom{Val{TopLevel}}(Val{TopLevel}(#undef))))

julia>     v = TopLevel[x, x] # impossible 2 instances
2-element Vector{TopLevel}:
 TopLevel(TopLevel(Mid{Val{TopLevel}}(Val{TopLevel}(#undef), Bottom{Val{TopLevel}}(Val{TopLevel}(#undef)))))
 TopLevel(TopLevel(Mid{Val{TopLevel}}(Val{TopLevel}(#undef), Bottom{Val{TopLevel}}(Val{TopLevel}(#undef)))))

julia> TopLevel[x] # fine at 1
1-element Vector{TopLevel}:
 TopLevel(Mid{Val{TopLevel}}(Val{TopLevel}(#undef), Bottom{Val{TopLevel}}(Val{TopLevel}(#undef))))

julia> TopLevel[x,x,x] # fine at 3
3-element Vector{TopLevel}:
 TopLevel(Mid{Val{TopLevel}}(Val{TopLevel}(#undef), Bottom{Val{TopLevel}}(Val{TopLevel}(#undef))))
 TopLevel(Mid{Val{TopLevel}}(Val{TopLevel}(#undef), Bottom{Val{TopLevel}}(Val{TopLevel}(#undef))))
 TopLevel(Mid{Val{TopLevel}}(Val{TopLevel}(#undef), Bottom{Val{TopLevel}}(Val{TopLevel}(#undef))))

I did not realize that the first example specifically fails for a vector of length 2. Seems however that the second example fails (still somewhat randomly) for any length.

In any case, if someone considers this worth reporting as its own bug, please feel free to do so if you want.