Skip to content

Commit

Permalink
use getproperty
Browse files Browse the repository at this point in the history
  • Loading branch information
CarloLucibello committed Mar 30, 2024
1 parent da2e416 commit 3856252
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 14 deletions.
42 changes: 37 additions & 5 deletions src/keypath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,46 @@ A type for representing a path of keys to a value in a nested structure.
Can be constructed with a sequence of keys, or by concatenating other `KeyPath`s.
Keys can be of type `Symbol`, `String`, or `Int`.
For custom types, access through symbol keys is assumed to be done with `getproperty`.
For consistency, the method `Base.propertynames` is used to get the viable property names.
For string and integer keys instead, the access is done with `getindex`.
See also [`getkeypath`](@ref), [`haskeypath`](@ref).
# Examples
```jldoctest
julia> kp = KeyPath(:b, 3)
KeyPath(:b, 3)
julia> KeyPath(:a, kp, :c, 4)
julia> KeyPath(:a, kp, :c, 4) # construct mixing keys and keypaths
KeyPath(:a, :b, 3, :c, 4)
julia> struct T
a
b
end
julia> function Base.getproperty(x::T, k::Symbol)
if k in fieldnames(T)
return getfield(x, k)
elseif k === :ab
return "ab"
else
error()
end
end;
julia> Base.propertynames(::T) = (:a, :b, :ab);
julia> x = T(3, Dict(:c => 4, :d => 5));
julia> getkeypath(x, KeyPath(:ab)) # equivalent to x.ab
"ab"
julia> getkeypath(x, KeyPath(:b, :c)) # equivalent to (x.b)[:c]
4
```
"""
struct KeyPath{T<:Tuple}
Expand Down Expand Up @@ -52,14 +84,14 @@ end
keypathstr(kp::KeyPath) = join(kp.keys, ".")

_getkey(x, k::Integer) = x[k]
_getkey(x, k::Symbol) = getfield(x, k)
_getkey(x, k::Symbol) = getproperty(x, k)
_getkey(x::AbstractDict, k::Symbol) = x[k]
_getkey(x, k::AbstractString) = x[k]

_haskey(x, k::Integer) = haskey(x, k)
_haskey(x::Tuple, k::Integer) = 1 <= k <= length(x)
_haskey(x::AbstractArray, k::Integer) = 1 <= k <= length(x) # TODO: extend to generic indexing
_haskey(x, k::Symbol) = k in fieldnames(typeof(x))
_haskey(x, k::Symbol) = k in propertynames(x)
_haskey(x::AbstractDict, k::Symbol) = haskey(x, k)
_haskey(x, k::AbstractString) = haskey(x, k)

Expand All @@ -68,7 +100,7 @@ _haskey(x, k::AbstractString) = haskey(x, k)
Return the value in `x` at the path `kp`.
See also [`haskeypath`](@ref).
See also [`KeyPath`](@ref) and [`haskeypath`](@ref).
# Examples
```jldoctest
Expand All @@ -94,7 +126,7 @@ end
Return `true` if `x` has a value at the path `kp`.
See also [`getkeypath`](@ref).
See also [`KeyPath`](@ref) and [`getkeypath`](@ref).
# Examples
```jldoctest
Expand Down
9 changes: 6 additions & 3 deletions test/basics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -400,10 +400,13 @@ end
@functor A
a = A(1)
@test Functors.children(a) === (x = 1,)
Functors.@leaf A
children, re = Functors.functor(a)

struct B; x; end
Functors.@leaf B
b = B(1)
children, re = Functors.functor(b)
@test children == Functors.NoChildren()
@test re(children) === a
@test re(children) === b
end

@testset "IterateWalk" begin
Expand Down
40 changes: 34 additions & 6 deletions test/keypath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,40 @@
kp0 = KeyPath()
@test (kp0...,) === ()

struct Tkp
a
b
c
end

function Base.getproperty(x::Tkp, k::Symbol)
if k in fieldnames(Tkp)
return getfield(x, k)
elseif k === :ab
return "ab"
else
error()
end
end

Base.propertynames(::Tkp) = (:a, :b, :c, :ab)

@testset "getkeypath" begin
x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7]))
@test getkeypath(x, KeyPath(:a)) == 3
@test getkeypath(x, KeyPath(:b, :c)) == 4
@test getkeypath(x, KeyPath(:b, "d", 2)) == 6

struct Tkp
a
b
c
end

x = Tkp(3, Tkp(4, 5, (6, 7)), 8)
kp = KeyPath(:b, :c, 2)
@test getkeypath(x, kp) == 7

@testset "access through getproperty" begin
x = Tkp(3, Dict(:c => 4, :d => 5), 6);

@test getkeypath(x, KeyPath(:ab)) == "ab"
@test getkeypath(x, KeyPath(:b, :c)) == 4
end
end

@testset "haskeypath" begin
Expand All @@ -39,5 +59,13 @@
@test haskeypath(x, KeyPath(:b, "d", 2))
@test !haskeypath(x, KeyPath(:b, "d", 4))
@test !haskeypath(x, KeyPath(:b, "e"))

@testset "access through getproperty" begin
x = Tkp(3, Dict(:c => 4, :d => 5), 6);

@test haskeypath(x, KeyPath(:ab))
@test haskeypath(x, KeyPath(:b, :c))
@test !haskeypath(x, KeyPath(:b, :e))
end
end
end

0 comments on commit 3856252

Please sign in to comment.