Type-inference and the dot operator

I had naively assumed the type-inference used only the types of the arguments to a function call then realized that in an important special case - the dot operator - it must be able to use the value of the second argument. For a composite type foo the type of foo.bar depends on the value, :bar, not just the fact that this argument is of type Symbol.

I can see how it would be possible to infer the type of foo.bar in the days when the foo.bar meant getfield(foo, :bar) but now that it means getproperty(foo, :bar) and it is possible to have custom getproperty methods for a type I’m a bit confused as to how it works. Is there an explanation somewhere of how type inference reacts with constant propagation for cases like this?

1 Like

give a MWE

There’s a number of things that come into play here. The biggest is that in simple cases, the getproperty call will be inlined and turned into a simple getfield (when there isn’t a getproperty override for the type). Since the symbol is also const it will be possible for constant prop/semi-concrete eval to figure out what’s happening. That said, with sufficiently complex getproperty overloads it is very possible to make foo.bar uninferrable.

3 Likes

That’s pretty much the conclusion I had come to from looking at some examples.

julia> struct foo{T}
         a::T
         b::Int
       end

julia> tt = foo(1.234, 5)
foo{Float64}(1.234, 5)

julia> @code_warntype tt.b
MethodInstance for getproperty(::foo{Float64}, ::Symbol)
  from getproperty(x, f::Symbol) @ Base Base.jl:37
Arguments
  #self#::Core.Const(getproperty)
  x::foo{Float64}
  f::Symbol
Body::Union{Float64, Int64}
1 ─      nothing
│   %2 = Base.getfield(x, f)::Union{Float64, Int64}
└──      return %2


julia> function getb(f::foo)
         (; a, b) = f
         return b
       end
getb (generic function with 1 method)

julia> @code_warntype getb(tt)
MethodInstance for getb(::foo{Float64})
  from getb(f::foo) @ Main REPL[7]:1
Arguments
  #self#::Core.Const(getb)
  f::foo{Float64}
Locals
  b::Int64
  a::Float64
Body::Int64
1 ─     (a = Base.getproperty(f, :a))
│       (b = Base.getproperty(f, :b))
└──     return b

If calls like getproperty(f, :a) occur inside another function further type inference can be carried out and the type reduced from a Union to a concrete type.

The fact the sufficiently complicated custom getproperty methods can interfere with this can change the balance of writing “getter” methods versus using properties. Often there are attributes of statistical models that are best evaluated on the fly from other information in the model struct and I have been adding cases in custom getproperty methods for these rather than defining (and documenting) extractor functions. I should reconsider that approach.