Accessing the field of `Pair` seems to be type-unstable

MWE (Julia v1.6 & v1.7):

julia> p1 = Pair(:a, 2)
:a => 2

julia> @code_warntype p1.first
Variables
  #self#::Core.Const(getproperty)
  x::Pair{Symbol, Int64}
  f::Symbol

Body::Union{Int64, Symbol}
1 ─ %1 = Base.getfield(x, f)::Union{Int64, Symbol}
└──      return %1

julia> @code_warntype p1[1]
Variables
  #self#::Core.Const(getindex)
  p::Pair{Symbol, Int64}
  i::Int64

Body::Union{Int64, Symbol}
1 ─ %1 = Base.getfield(p, i)::Union{Int64, Symbol}
└──      return %1

julia> @code_warntype p1.second
Variables
  #self#::Core.Const(getproperty)
  x::Pair{Symbol, Int64}
  f::Symbol

Body::Union{Int64, Symbol}
1 ─ %1 = Base.getfield(x, f)::Union{Int64, Symbol}
└──      return %1

julia> @code_warntype p1[2]
Variables
  #self#::Core.Const(getindex)
  p::Pair{Symbol, Int64}
  i::Int64

Body::Union{Int64, Symbol}
1 ─ %1 = Base.getfield(p, i)::Union{Int64, Symbol}
└──      return %1

Is there a reason for this result and can’t it be designed to be type-stable? Thanks!

You need to do introspection like this in a function:

julia> p = Pair(:a, 2);

julia> f(x) = x.first;

julia> @code_warntype f(p)
MethodInstance for f(::Pair{Symbol, Int64})
  from f(x) in Main at REPL[5]:1
Arguments
  #self#::Core.Const(f)
  x::Pair{Symbol, Int64}
Body::Symbol
1 ─ %1 = Base.getproperty(x, :first)::Symbol
└──      return %1
2 Likes

Thanks! Do you know why if I don’t wrap it in a function it is shown to be type-unstable?

To elaborate, @code_warntype only sees the types of the arguments to the function call you give it.
The syntax p1.first is just a shorthand for getproperty(p1, :first), so when you do: @code_warntype p1.first, you’re seeing the result of:

code_warntype(getproperty, (typeof(p1), typeof(:first)))

or, in other words:

code_warntype(getproperty, (Pair{Symbol, Int}, Symbol))

which isn’t enough information to indicate whether you’re accessing the first or second element.

Putting the access into a function lets Julia’s usual constant propagation machinery do its work, and @code_warntype can correctly show you that result when you use it on the new function.

8 Likes