Surprising type widening when constructing Tuple{...}

Dear Julia type inference masters,

I am facing some inference/performance problems which look like they could be circumvented, but I don’t know enough about what is going on here.

struct ValType{T, V} end
ValTypeof(v::T) where T = ValType{T, v}

surprising_type_widening(i) = Tuple{Int, ValTypeof(i)}
@code_warntype surprising_type_widening(3)


MethodInstance for surprising_type_widening(::Int64)
  from surprising_type_widening(i) in Main at REPL[6]:1
Body::Type{<:Tuple{Int64, Any}}
1 ─ %1 = Main.ValTypeof(i)::Type{ValType{Int64, _A}} where _A
│   %2 = Core.apply_type(Main.Tuple, Main.Int, %1)::Type{<:Tuple{Int64, Any}}
└──      return %2

where you can see that despite it could have inferred Type{<:Tuple{Int64, ValType{Int64}}}, it widens to Type{<:Tuple{Int64, Any}}

any help about why this happens and how to circumvent it is highly appreciated
(tested on julia version 1.8.1 and nightly 1.10.0-DEV.350)

I mean this is the classical type instability right? your output type depends on value of v, not type of v

you can workaround this by using Val()

julia> ValTypeof(::Val{v}) where v = ValType{typeof(v), v}
ValTypeof (generic function with 2 methods)

julia> ValTypeof(Val(3))
ValType{Int64, 3}

julia> @code_warntype ValTypeof(Val(3))
MethodInstance for ValTypeof(::Val{3})
  from ValTypeof(::Val{v}) where v @ Main REPL[15]:1
Static Parameters
  v = 3
Body::Type{ValType{Int64, 3}}
1 ─ %1 = Main.ValType::Core.Const(ValType)
│   %2 = Main.typeof($(Expr(:static_parameter, 1)))::Core.Const(Int64)
│   %3 = Core.apply_type(%1, %2, $(Expr(:static_parameter, 1)))::Core.Const(ValType{Int64, 3})
└──      return %3
1 Like

yes, I agree this is a type instability. Such instabilities occur in my code.

What I would like to improve is the widening strategy. Currently my instable ValTypes are widened to Any, while I would like them to be widened to ValType{Int}, which at least seems intuitively possible.