# Overloading \`getproperty\` in a type-stable manner

**URL:** <https://discourse.julialang.org/t/overloading-getproperty-in-a-type-stable-manner/36638>\
**Category:** General Usage\
**Tags:** question\
**Created:** [March 28, 2020, 2:19pm UTC](https://discourse.julialang.org/t/overloading-getproperty-in-a-type-stable-manner/36638 "2020-03-28T14:19:09Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Datseris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/datseris/32/13406_2.png) [@Datseris](https://discourse.julialang.org/u/Datseris)\
**Post date:** [March 28, 2020, 2:19pm UTC](https://discourse.julialang.org/t/overloading-getproperty-in-a-type-stable-manner/36638/1 "2020-03-28T14:19:09Z")

</div>

Hi, I am trying to overload `getproperty` for a struct I have.

I do:

```julia
function Base.getproperty(m::ABM, s::Symbol)
    if s ∈ (:agents, :space, :scheduler, :properties)
        return Base.getfield(m, s)
    else
        return Base.getindex(m.properties, s)
    end
end

```

So, my end goal is to still be able to write `model.agents` and get the actual field of the struct, but if I type `model.s` to get the value of `[:s]` from the underlying dictionary. The method as I have written it above works just fine and does what it should.

Now, **before** adding this method, I do:

```julia

julia> @btime $(model).properties[:ϵ]
  9.000 ns (0 allocations: 0 bytes)
0.4

julia> @btime $(model).scheduler
  0.001 ns (0 allocations: 0 bytes)
fastest (generic function with 1 method)

julia> @code_warntype model.properties[:ϵ]
Variables
  #self#::Core.Compiler.Const(getindex, false)
  h::Dict{Symbol,Float64}
  key::Symbol
  val::Union{}
  index::Int64

Body::Float64
1 ─ Core.NewvarNode(:(val))
│ (index = Base.ht_keyindex(h, key))
│ $(Expr(:inbounds, true))
│ %4 = (index < 0)::Bool
└── goto #3 if not %4
2 ─ %6 = Base.KeyError(key)::KeyError
│ Base.throw(%6)
└── Core.Compiler.Const(:(return %7), false)
3 ┄ %9 = Base.getproperty(h, :vals)::Array{Float64,1}
│ %10 = Base.getindex(%9, index)::Float64
│ %11 = Core.typeassert(%10, $(Expr(:static_parameter, 2)))::Float64
└── return %11
4 ─ $(Expr(:inbounds, :pop))
└── Core.Compiler.Const(:(return val), false)

```

I then add the method, and I have:

```julia
julia> @btime $(model).scheduler
  17.735 ns (0 allocations: 0 bytes)
fastest (generic function with 1 method)

julia> @btime $(model).properties[:ϵ]
  49.545 ns (1 allocation: 16 bytes)
0.4

julia> @btime $(model).ϵ
  49.848 ns (1 allocation: 16 bytes)
0.4

julia> @code_warntype model.ϵ
Variables
  #self#::Core.Compiler.Const(getproperty, false)
  m::AgentBasedModel{HKAgent,Nothing,typeof(fastest),Dict{Symbol,Float64}}   
  s::Symbol

Body::Any
1 ─ %1 = (:agents, :space, :scheduler, :properties)::Core.Compiler.Const((:agents, :space, :scheduler, :properties), false)
│ %2 = (s ∈ %1)::Bool
└── goto #3 if not %2
2 ─ %4 = Base.getfield::Core.Compiler.Const(getfield, false)
│ %5 = (%4)(m, s)::Union{typeof(fastest), Nothing, Dict}
└── return %5
3 ─ %7 = Base.getindex::Core.Compiler.Const(getindex, false)
│ %8 = Base.getproperty(m, :properties)::Any
│ %9 = (%7)(%8, s)::Any
└── return %9

```

Performance dropped and I have type instability. Is there a way to achieve what I want, or is it strictly impossible to use `getproperty` to do _both_ `getfield` as well as something else?

---

<div class="post-metadata">

**Author:** ![Datseris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/datseris/32/13406_2.png) [@Datseris](https://discourse.julialang.org/u/Datseris)\
**Post date:** [March 28, 2020, 2:52pm UTC](https://discourse.julialang.org/t/overloading-getproperty-in-a-type-stable-manner/36638/2 "2020-03-28T14:52:34Z")

</div>

Fredrik suggested that I try instead

```julia
function Base.getproperty(m::ABM{A, S, F, P}, s::Symbol) where {A, S, F, P}
    if s === :agents
        return Base.getfield(m, :agents)
    elseif s === :space
        return Base.getfield(m, :space)
    elseif s === :scheduler
        return Base.getfield(m, :scheduler)
    elseif s === :properties
        return Base.getfield(m, :properties)
    elseif P <: Dict
        return Base.getindex(m.properties, s)
    else # properties is assumed to be a struct
        return Base.getproperty(m.properties, s)
    end
end

```

and test using functions, so that constant propagation occurs. So I did:

```julia
test1(model) = model.a
@inferred test1(model1)

```

but this also didn’t infer: `ERROR: return type Float64 does not match inferred return type Any`

---

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [March 28, 2020, 2:57pm UTC](https://discourse.julialang.org/t/overloading-getproperty-in-a-type-stable-manner/36638/3 "2020-03-28T14:57:56Z")

</div>

I would try replacing `m.properties` on the 5th line with `getfield(m, :properties)`. This recursion could be the reason why your `getproperty` is not inferred here.

---

<div class="post-metadata">

**Author:** ![Datseris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/datseris/32/13406_2.png) [@Datseris](https://discourse.julialang.org/u/Datseris)\
**Post date:** [March 28, 2020, 2:59pm UTC](https://discourse.julialang.org/t/overloading-getproperty-in-a-type-stable-manner/36638/4 "2020-03-28T14:59:01Z")

</div>

Update!

using:

```julia
function Base.getproperty(m::ABM{A, S, F, P}, s::Symbol) where {A, S, F, P}
    if s === :agents
        return getfield(m, :agents)
    elseif s === :space
        return getfield(m, :space)
    elseif s === :scheduler
        return getfield(m, :scheduler)
    elseif s === :properties
        return getfield(m, :properties)
    elseif P <: Dict
        return getindex(getfield(m, :properties), s)
    else # properties is assumed to be a struct
        return getproperty(getfield(m, :properties), s)
    end
end

```

and restarting Julia to clear everything works, but **one must be sure to test this inside functions** so that constant propagation occurs!

@simeonschaub Hah I just saw your answer! You say the same thing!

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [March 28, 2020, 6:30pm UTC](https://discourse.julialang.org/t/overloading-getproperty-in-a-type-stable-manner/36638/5 "2020-03-28T18:30:50Z")

</div>

> [@Unexpected type instability with getproperty but not setproperty!](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/10):
>
> Constant propagation doesn’t really work with recursion. Use: function Base.getproperty(e::Entry, s::Symbol) if s === :value getfield(e, :data)[getfield(e, :id)] else getfield(e, s) end end
