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
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.