Type stable specializations of `getproperty` without code duplication?

Avoiding potential self-recursion should help (if allowed in your real use case):

julia> module Baz

       export Bar, Foo

       struct Bar{U}
           u::U
       end

       struct Foo{B, T, ABC}
           bar::B
           xy::T
           abc::ABC
       end

       macro _getproperty(x, s)
           return :(
               if $(esc(s)) === :x
                   return getfield($(esc(x)), :xy)[1]
               elseif $(esc(s)) === :a
                   return getfield($(esc(x)), :abc)[1]
               elseif $(esc(s)) === :b
                   return getfield($(esc(x)), :abc)[2]
               else 
                   return getfield($(esc(x)), $(esc(s)))
               end
           )
       end

       # `bar`-specific (and type unstable) `getproperty` code
       function Base.getproperty(x::Foo{<:Bar}, s::Symbol)
           if s === :u
               return length(getfield(getfield(x, :bar), :u))
           else
               @_getproperty(x, s)
           end
       end

       end # module
Main.Baz

julia> using .Baz

julia> bar = Bar(1)
Bar{Int64}(1)

julia> x = Foo(bar, (1., 2), "ab")
Foo{Bar{Int64}, Tuple{Float64, Int64}, String}(Bar{Int64}(1), (1.0, 2), "ab")

julia> using Test

julia> function f(x::Foo)
           x.x, x.xy, x.a, x.u
       end
f (generic function with 1 method)

julia> @inferred f(x)
(1.0, (1.0, 2), 'a', 1)

julia> g(x) = x.u
g (generic function with 1 method)

julia> @code_warntype g(x)
MethodInstance for g(::Foo{Bar{Int64}, Tuple{Float64, Int64}, String})
  from g(x) in Main at REPL[8]:1
Arguments
  #self#::Core.Const(g)
  x::Foo{Bar{Int64}, Tuple{Float64, Int64}, String}
Body::Int64
1 ─ %1 = Base.getproperty(x, :u)::Core.Const(1)
└──      return %1

Note that I used return length(getfield(getfield(x, :bar), :u)) instead of return length(getfield(x.bar, :u)).

3 Likes