Skip to content

MWE: if x isa T does not narrow the type in else block #35566

@tkf

Description

@tkf

This snippet

@noinline function step(acc, x)
    xs, = acc
    y = x > 0.0 ? x : missing
    if y isa eltype(xs)
        ys = push!(xs, y)
    else
        ys = vcat(xs, [y])
    end
    return (ys,)
end

@noinline function _foldl_iter(rf, val::T, iter, state) where {T}
    while true
        ret = iterate(iter, state)
        ret === nothing && break
        x, state = ret
        y = rf(val, x)
        if y isa T
            val = y
        else
            return _foldl_iter(rf, y, iter, state)
        end
    end
    return val
end

@code_typed optimize=false _foldl_iter(step, (Missing[],), [0.0], 1)

prints

CodeInfo(
1$(Expr(:meta, :noinline))
│         (val@_10 = val@_3)::Tuple{Array{Missing,1}}
└──       (state@_11 = state@_5)::Int64
2 ┄       goto #9 if not true
3 ─       Core.NewvarNode(:(x))::Any
│         Core.NewvarNode(:(@_8))::Any
│         Core.NewvarNode(:(y))::Any
│         (ret = Main.iterate(iter, state@_11))::Union{Nothing, Tuple{Float64,Int64}}%9  = (ret === Main.nothing)::Bool
└──       goto #5 if not %9
4 ─       goto #9
5%12 = Base.indexed_iterate(ret::Tuple{Float64,Int64}, 1)::Core.Compiler.PartialStruct(Tuple{Float64,Int64}, Any[Float64, Core.Compiler.Const(2, false)])
│         (x = Core.getfield(%12, 1))::Float64
│         (@_8 = Core.getfield(%12, 2))::Core.Compiler.Const(2, false)
│   %15 = Base.indexed_iterate(ret::Tuple{Float64,Int64}, 2, @_8::Core.Compiler.Const(2, false))::Core.Compiler.PartialStruct(Tuple{Int64,Int64}, Any[Int64, Core.Compiler.Const(3, false)])
│         (state@_11 = Core.getfield(%15, 1))::Int64
│         (y = (rf)(val@_10, x))::Tuple{Union{Array{Missing,1}, Array{Union{Missing, Float64},1}}}
│   %18 = (y isa $(Expr(:static_parameter, 1)))::Bool
└──       goto #7 if not %18
6 ─       (val@_10 = y::Tuple{Array{Missing,1}})::Tuple{Array{Missing,1}}
└──       goto #8
7%22 = Main._foldl_iter(rf, y, iter, state@_11)::Tuple{Union{Array{Missing,1}, Array{Union{Missing, Float64},1}}}
└──       return %22
8 ─       goto #2
9return val@_10
) => Tuple{Union{Array{Missing,1}, Array{Union{Missing, Float64},1}}}

I expect %22 = Main._foldl_iter(rf, y, iter, state@_11)::Tuple{Array{Union{Missing, Float64},1}} instead of %22 = Main._foldl_iter(rf, y, iter, state@_11)::Tuple{Union{Array{Missing,1}, Array{Union{Missing, Float64},1}}} which has a tuple-of-union. This seems to be due that the argument y in #7 is inferred to be Tuple{Union{Array{Missing,1}, Array{Union{Missing, Float64},1}}}. (Indeed, this is what @descend shows me. Also, I see a corresponding @jl_apply_generic in the LLVM IR. IIUC this is a sign of a dynamic dispatch.)

The type is narrowed as expected if I tweak the example to avoid using intermediate tuple:

@noinline function step2(xs, x)
    y = x > 0.0 ? x : missing
    if y isa eltype(xs)
        ys = push!(xs, y)
    else
        ys = vcat(xs, [y])
    end
    return ys
end

@code_typed optimize=false _foldl_iter(step2, Missing[], [0.0], 1)

prints

...
7%22 = Main._foldl_iter(rf, y::Array{Union{Missing, Float64},1}, iter, state@_11)::Array{Union{Missing, Float64},1}
...

It'd be great if this works in the first example too.

Checked with Julia 1.5.0-DEV.630.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions