The question is basically, which is “better” in terms of intermediate type-stability and/or performance:
function half_promote(x :: T) :: T where T <: AbstractFloat
    half = 1//2
    return half * x
end
or
function half_convert(x :: T) :: T where T <: AbstractFloat
    half = oftype(x, 1//2)
    return half * x
end
?
For type-stability I look at the @code_warntype (for which the man-page is good but doesn’t seem to answer my question) below, but I can’t seem to extract a meaningful difference other than the explicit conversion in the latter.
julia> @code_warntype half_promote(2.0)
MethodInstance for half_promote(::Float64)
  from half_promote(x::T) where T<:AbstractFloat @ Main REPL[6]:1
Static Parameters
  T = Float64
Arguments
  #self#::Core.Const(Main.half_promote)
  x::Float64
Locals
  half::Rational{Int64}
  @_4::Float64
Body::Float64
1 ─ %1  = $(Expr(:static_parameter, 1))::Core.Const(Float64)
│         (half = 1 // 2)
│   %3  = half::Core.Const(1//2)
│   %4  = (%3 * x)::Float64
│         (@_4 = %4)
│   %6  = @_4::Float64
│   %7  = (%6 isa %1)::Core.Const(true)
└──       goto #3 if not %7
2 ─       goto #4
3 ─       Core.Const(:(@_4))
│         Core.Const(:(Base.convert(%1, %10)))
└──       Core.Const(:(@_4 = Core.typeassert(%11, %1)))
4 ┄ %13 = @_4::Float64
└──       return %13
and
julia> @code_warntype half_convert(2.0)
MethodInstance for half_convert(::Float64)
  from half_convert(x::T) where T<:AbstractFloat @ Main REPL[7]:1
Static Parameters
  T = Float64
Arguments
  #self#::Core.Const(Main.half_convert)
  x::Float64
Locals
  half::Float64
  @_4::Float64
Body::Float64
1 ─ %1  = $(Expr(:static_parameter, 1))::Core.Const(Float64)
│   %2  = Main.oftype::Core.Const(oftype)
│   %3  = (1 // 2)::Core.Const(1//2)
│         (half = (%2)(x, %3))
│   %5  = half::Core.Const(0.5)
│   %6  = (%5 * x)::Float64
│         (@_4 = %6)
│   %8  = @_4::Float64
│   %9  = (%8 isa %1)::Core.Const(true)
└──       goto #3 if not %9
2 ─       goto #4
3 ─       Core.Const(:(@_4))
│         Core.Const(:(Base.convert(%1, %12)))
└──       Core.Const(:(@_4 = Core.typeassert(%13, %1)))
4 ┄ %15 = @_4::Float64
└──       return %15
Thankfully, the LLVM lowering is a little more concise and provides nearly identical results with the only difference being in the comments.
julia> @code_llvm half_promote(2.0)
; Function Signature: half_promote(Float64)
;  @ REPL[6]:1 within `half_promote`
define double @julia_half_promote_10502(double %"x::Float64") #0 {
top:
;  @ REPL[6]:3 within `half_promote`
; ┌ @ promotion.jl:430 within `*` @ float.jl:493
   %0 = fmul double %"x::Float64", 5.000000e-01
; └
  ret double %0
}
and
julia> @code_llvm half_convert(2.0)
; Function Signature: half_convert(Float64)
;  @ REPL[7]:1 within `half_convert`
define double @julia_half_convert_10519(double %"x::Float64") #0 {
top:
;  @ REPL[7]:3 within `half_convert`
; ┌ @ float.jl:493 within `*`
   %0 = fmul double %"x::Float64", 5.000000e-01
; └
  ret double %0
}
So does the identical IR actually mean there is literally no difference between these functions?