From 3a7097d4adbad8994cd631cf8edc56b3e242aa83 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 27 Aug 2022 00:35:56 -0400 Subject: [PATCH 01/10] Use `pd` as standard symbol for any `PropertyDict` --- src/PropertyDicts.jl | 109 ++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index 936193f..60b2620 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -5,56 +5,57 @@ export PropertyDict struct PropertyDict{K, V, T <: AbstractDict{K, V}} <: AbstractDict{K, V} d::T + PropertyDict(@nospecialize pd::PropertyDict) = pd PropertyDict(d::T) where {T <: AbstractDict} = new{keytype(d), valtype(d), T}(d) PropertyDict(args...) = PropertyDict(Dict(args...)) end -unwrap(d::PropertyDict) = getfield(d, :d) +unwrap(pd::PropertyDict) = getfield(pd, :d) -function Base.sizehint!(d::PropertyDict, n::Integer) - sizehint!(unwrap(d), n) - return d +function Base.sizehint!(pd::PropertyDict, n::Integer) + sizehint!(getfield(pd, :d), n) + return pd end -Base.push!(d::PropertyDict, p::Pair) = push!(unwrap(d), p) -Base.pop!(d::PropertyDict, args...) = pop!(unwrap(d), args...) -function Base.empty!(d::PropertyDict) - empty!(unwrap(d)) - return d +Base.push!(pd::PropertyDict, p::Pair) = push!(getfield(pd, :d), p) +Base.pop!(pd::PropertyDict, args...) = pop!(getfield(pd, :d), args...) +function Base.empty!(pd::PropertyDict) + empty!(getfield(pd, :d)) + return pd end -function Base.delete!(d::PropertyDict, key) - delete!(unwrap(d), key) - return d +function Base.delete!(pd::PropertyDict, key) + delete!(getfield(pd, :d), key) + return pd end -Base.getproperty(d::PropertyDict, n::Symbol) = getindex(d, n) -Base.getproperty(d::PropertyDict, n::String) = getindex(d, n) +Base.getproperty(pd::PropertyDict, n::Symbol) = getindex(pd, n) +Base.getproperty(pd::PropertyDict, n::String) = getindex(pd, n) -Base.setproperty!(d::PropertyDict, n::Symbol, v) = setindex!(d, v, n) -Base.setproperty!(d::PropertyDict, n::String, v) = setindex!(d, v, n) +Base.setproperty!(pd::PropertyDict, n::Symbol, v) = setindex!(pd, v, n) +Base.setproperty!(pd::PropertyDict, n::String, v) = setindex!(pd, v, n) -Base.get(d::PropertyDict, k, default) = get(unwrap(d), k, default) -Base.get(d::PropertyDict{Symbol}, k::AbstractString, default) = get(d, Symbol(k), default) -Base.get(d::PropertyDict{<:AbstractString}, k::Symbol, default) = get(d, String(k), default) -function Base.get(d::PropertyDict{Any}, k::Symbol, default) - out = get(unwrap(d), k, Base.secret_table_token) +Base.get(pd::PropertyDict, k, default) = get(getfield(pd, :d), k, default) +Base.get(pd::PropertyDict{Symbol}, k::AbstractString, default) = get(pd, Symbol(k), default) +Base.get(pd::PropertyDict{<:AbstractString}, k::Symbol, default) = get(pd, String(k), default) +function Base.get(pd::PropertyDict{Any}, k::Symbol, default) + out = get(getfield(pd, :d), k, Base.secret_table_token) if out === Base.secret_table_token - return get(unwrap(d), String(k), default) + return get(getfield(pd, :d), String(k), default) else return out end end -function Base.get(d::PropertyDict{Any}, k::String, default) - out = get(unwrap(d), k, Base.secret_table_token) +function Base.get(pd::PropertyDict{Any}, k::String, default) + out = get(getfield(pd, :d), k, Base.secret_table_token) if out === Base.secret_table_token - return get(unwrap(d), Symbol(k), default) + return get(getfield(pd, :d), Symbol(k), default) else return out end end -function Base.get(f::Union{Function,Type}, d::PropertyDict, k) - out = get(d, k, Base.secret_table_token) +function Base.get(f::Union{Function,Type}, pd::PropertyDict, k) + out = get(pd, k, Base.secret_table_token) if out === Base.secret_table_token return f() else @@ -62,58 +63,58 @@ function Base.get(f::Union{Function,Type}, d::PropertyDict, k) end end -Base.get!(d::PropertyDict, k, default) = get!(unwrap(d), k, default) -Base.get!(d::PropertyDict{Symbol}, k::AbstractString, default) = get!(d, Symbol(k), default) -Base.get!(d::PropertyDict{<:AbstractString}, k::Symbol, default) = get!(d, String(k), default) -function Base.get!(f::Union{Function,Type}, d::PropertyDict, k) - out = get(d, k, Base.secret_table_token) +Base.get!(pd::PropertyDict, k, default) = get!(getfield(pd, :d), k, default) +Base.get!(pd::PropertyDict{Symbol}, k::AbstractString, default) = get!(pd, Symbol(k), default) +Base.get!(pd::PropertyDict{<:AbstractString}, k::Symbol, default) = get!(pd, String(k), default) +function Base.get!(f::Union{Function,Type}, pd::PropertyDict, k) + out = get(pd, k, Base.secret_table_token) if out === Base.secret_table_token default = f() - setindex!(d, default, k) + setindex!(pd, default, k) return default else return out end end -function Base.getindex(d::PropertyDict, k) - out = get(d, k, Base.secret_table_token) +function Base.getindex(pd::PropertyDict, k) + out = get(pd, k, Base.secret_table_token) out === Base.secret_table_token && throw(KeyError(k)) return out end -Base.setindex!(d::PropertyDict, v, i) = setindex!(unwrap(d), v, i) -Base.setindex!(d::PropertyDict{<:AbstractString}, v, i::Symbol) = setindex!(d, v, String(i)) -Base.setindex!(d::PropertyDict{Symbol}, v, i::AbstractString) = setindex!(d, v, Symbol(i)) +Base.setindex!(pd::PropertyDict, v, i) = setindex!(getfield(pd, :d), v, i) +Base.setindex!(pd::PropertyDict{<:AbstractString}, v, i::Symbol) = setindex!(pd, v, String(i)) +Base.setindex!(pd::PropertyDict{Symbol}, v, i::AbstractString) = setindex!(pd, v, Symbol(i)) -Base.iterate(d::PropertyDict) = iterate(unwrap(d)) -Base.iterate(d::PropertyDict, i) = iterate(unwrap(d), i) +Base.iterate(pd::PropertyDict) = iterate(getfield(pd, :d)) +Base.iterate(pd::PropertyDict, i) = iterate(getfield(pd, :d), i) Base.IteratorSize(::Type{PropertyDict{K,V,T}}) where {K,V,T} = Base.IteratorSize(T) Base.IteratorEltype(::Type{PropertyDict{K,V,T}}) where {K,V,T} = Base.IteratorEltype(T) -Base.length(d::PropertyDict) = length(unwrap(d)) +Base.length(pd::PropertyDict) = length(getfield(pd, :d)) -Base.string(d::PropertyDict) = string(unwrap(d)) +Base.string(pd::PropertyDict) = string(getfield(pd, :d)) @static if isdefined(Base, :hasproperty) - Base.hasproperty(d::PropertyDict, key::Symbol) = haskey(d, key) - Base.hasproperty(d::PropertyDict, key) = haskey(d, key) + Base.hasproperty(pd::PropertyDict, key::Symbol) = haskey(pd, key) + Base.hasproperty(pd::PropertyDict, key) = haskey(pd, key) end -Base.haskey(d::PropertyDict, key) = haskey(unwrap(d), key) -Base.haskey(d::PropertyDict{<:AbstractString}, key::Symbol) = haskey(d, String(key)) -Base.haskey(d::PropertyDict{Symbol}, key::AbstractString) = haskey(d, Symbol(key)) -function Base.haskey(d::PropertyDict{Any}, key::AbstractString) - haskey(unwrap(d), key) || haskey(unwrap(d), Symbol(key)) +Base.haskey(pd::PropertyDict, key) = haskey(getfield(pd, :d), key) +Base.haskey(pd::PropertyDict{<:AbstractString}, key::Symbol) = haskey(pd, String(key)) +Base.haskey(pd::PropertyDict{Symbol}, key::AbstractString) = haskey(pd, Symbol(key)) +function Base.haskey(pd::PropertyDict{Any}, key::AbstractString) + haskey(getfield(pd, :d), key) || haskey(getfield(pd, :d), Symbol(key)) end -function Base.haskey(d::PropertyDict{Any}, key::Symbol) - haskey(unwrap(d), key) || haskey(unwrap(d), String(key)) +function Base.haskey(pd::PropertyDict{Any}, key::Symbol) + haskey(getfield(pd, :d), key) || haskey(getfield(pd, :d), String(key)) end # a handful of dictionaries aren't just wrapped in `KeySet` and `ValueIterator` -Base.keys(d::PropertyDict) = keys(unwrap(d)) -Base.values(d::PropertyDict) = values(unwrap(d)) +Base.keys(pd::PropertyDict) = keys(getfield(pd, :d)) +Base.values(pd::PropertyDict) = values(getfield(pd, :d)) -Base.propertynames(d::PropertyDict) = keys(d) +Base.propertynames(pd::PropertyDict) = keys(pd) end # module PropertyDicts From f5dd71f9980b2c235bf246b926b92392ea296fcd Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 27 Aug 2022 01:37:50 -0400 Subject: [PATCH 02/10] Greatly simplify code and reduce key inference issues. This enforces the key to be `Symbol` or `String`, which are the only types compatible with dot access anyway. It also allows us to combine a lot of code by using `_tokey(::PropertyDicts{K}, key)` to clearly convert `key` to the appropriate type. --- src/PropertyDicts.jl | 124 ++++++++++++++++--------------------------- test/runtests.jl | 4 -- 2 files changed, 47 insertions(+), 81 deletions(-) diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index 60b2620..9311a50 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -2,119 +2,89 @@ module PropertyDicts export PropertyDict -struct PropertyDict{K, V, T <: AbstractDict{K, V}} <: AbstractDict{K, V} +struct PropertyDict{K<:Union{String,Symbol}, V, T <: AbstractDict{K, V}} <: AbstractDict{K, V} d::T PropertyDict(@nospecialize pd::PropertyDict) = pd - PropertyDict(d::T) where {T <: AbstractDict} = new{keytype(d), valtype(d), T}(d) + PropertyDict(d::AbstractDict{String,V}) where {V} = new{String,V,typeof(d)}(d) + PropertyDict(d::AbstractDict{Symbol,V}) where {V} = new{Symbol,V,typeof(d)}(d) + function PropertyDict(d::AbstractDict) + dsym = Dict{Symbol,valtype(d)}() + for (k,v) in d + dsym[Symbol(k)] = v + end + PropertyDict(dsym) + end PropertyDict(args...) = PropertyDict(Dict(args...)) end -unwrap(pd::PropertyDict) = getfield(pd, :d) +Base.IteratorSize(@nospecialize T::Type{<:PropertyDict}) = Base.IteratorSize(fieldtype(T, :d)) +Base.IteratorEltype(@nospecialize T::Type{<:PropertyDict}) = Base.IteratorEltype(eltype(T)) + +Base.length(pd::PropertyDict) = length(getfield(pd, :d)) function Base.sizehint!(pd::PropertyDict, n::Integer) sizehint!(getfield(pd, :d), n) return pd end -Base.push!(pd::PropertyDict, p::Pair) = push!(getfield(pd, :d), p) -Base.pop!(pd::PropertyDict, args...) = pop!(getfield(pd, :d), args...) +_tokey(@nospecialize(pd::PropertyDict{String}), k::AbstractString) = k +_tokey(@nospecialize(pd::PropertyDict{String}), k) = String(k) +_tokey(@nospecialize(pd::PropertyDict{Symbol}), k::Symbol) = k +_tokey(@nospecialize(pd::PropertyDict{Symbol}), k) = Symbol(k) + +Base.pop!(pd::PropertyDict, k) = pop!(getfield(pd, :d), _tokey(pd, k)) +Base.pop!(pd::PropertyDict, k, d) = pop!(getfield(pd, :d), _tokey(pd, k), d) + function Base.empty!(pd::PropertyDict) empty!(getfield(pd, :d)) return pd end -function Base.delete!(pd::PropertyDict, key) - delete!(getfield(pd, :d), key) +function Base.delete!(pd::PropertyDict, k) + delete!(getfield(pd, :d), _tokey(pd, k)) return pd end +Base.empty(pd::PropertyDict) = PropertyDict(empty(getfield(pd, :d))) -Base.getproperty(pd::PropertyDict, n::Symbol) = getindex(pd, n) -Base.getproperty(pd::PropertyDict, n::String) = getindex(pd, n) - -Base.setproperty!(pd::PropertyDict, n::Symbol, v) = setindex!(pd, v, n) -Base.setproperty!(pd::PropertyDict, n::String, v) = setindex!(pd, v, n) - -Base.get(pd::PropertyDict, k, default) = get(getfield(pd, :d), k, default) -Base.get(pd::PropertyDict{Symbol}, k::AbstractString, default) = get(pd, Symbol(k), default) -Base.get(pd::PropertyDict{<:AbstractString}, k::Symbol, default) = get(pd, String(k), default) -function Base.get(pd::PropertyDict{Any}, k::Symbol, default) - out = get(getfield(pd, :d), k, Base.secret_table_token) - if out === Base.secret_table_token - return get(getfield(pd, :d), String(k), default) - else - return out - end -end -function Base.get(pd::PropertyDict{Any}, k::String, default) - out = get(getfield(pd, :d), k, Base.secret_table_token) - if out === Base.secret_table_token - return get(getfield(pd, :d), Symbol(k), default) - else - return out - end -end - +Base.get(pd::PropertyDict, k, d) = get(getfield(pd, :d), _tokey(pd, k), d) function Base.get(f::Union{Function,Type}, pd::PropertyDict, k) - out = get(pd, k, Base.secret_table_token) - if out === Base.secret_table_token - return f() - else - return out - end + get(f, getfield(pd, :d), _tokey(pd, k)) end - -Base.get!(pd::PropertyDict, k, default) = get!(getfield(pd, :d), k, default) -Base.get!(pd::PropertyDict{Symbol}, k::AbstractString, default) = get!(pd, Symbol(k), default) -Base.get!(pd::PropertyDict{<:AbstractString}, k::Symbol, default) = get!(pd, String(k), default) +Base.get!(pd::PropertyDict, k, d) = get!(getfield(pd, :d), _tokey(pd, k), d) function Base.get!(f::Union{Function,Type}, pd::PropertyDict, k) - out = get(pd, k, Base.secret_table_token) - if out === Base.secret_table_token - default = f() - setindex!(pd, default, k) - return default - else - return out - end + get!(f, getfield(pd, :d), _tokey(pd, k)) end -function Base.getindex(pd::PropertyDict, k) - out = get(pd, k, Base.secret_table_token) - out === Base.secret_table_token && throw(KeyError(k)) - return out +Base.@propagate_inbounds function Base.getindex(pd::PropertyDict, k) + getindex(getfield(pd, :d), _tokey(pd, k)) +end +Base.@propagate_inbounds function Base.setindex!(pd::PropertyDict, v, k) + setindex!(getfield(pd, :d), v, _tokey(pd, k)) end -Base.setindex!(pd::PropertyDict, v, i) = setindex!(getfield(pd, :d), v, i) -Base.setindex!(pd::PropertyDict{<:AbstractString}, v, i::Symbol) = setindex!(pd, v, String(i)) -Base.setindex!(pd::PropertyDict{Symbol}, v, i::AbstractString) = setindex!(pd, v, Symbol(i)) +Base.haskey(pd::PropertyDict, k) = haskey(getfield(pd, :d), _tokey(pd, k)) + +Base.getkey(pd::PropertyDict, k, d) = getkey(getfield(pd, :d), _tokey(pd, k), d) Base.iterate(pd::PropertyDict) = iterate(getfield(pd, :d)) Base.iterate(pd::PropertyDict, i) = iterate(getfield(pd, :d), i) -Base.IteratorSize(::Type{PropertyDict{K,V,T}}) where {K,V,T} = Base.IteratorSize(T) -Base.IteratorEltype(::Type{PropertyDict{K,V,T}}) where {K,V,T} = Base.IteratorEltype(T) +# a handful of dictionaries aren't just wrapped in `KeySet` and `ValueIterator` +Base.keys(pd::PropertyDict) = keys(getfield(pd, :d)) +Base.values(pd::PropertyDict) = values(getfield(pd, :d)) -Base.length(pd::PropertyDict) = length(getfield(pd, :d)) +## property methods +Base.getproperty(pd::PropertyDict, n::Symbol) = getindex(pd, n) +Base.getproperty(pd::PropertyDict, n::String) = getindex(pd, n) -Base.string(pd::PropertyDict) = string(getfield(pd, :d)) +Base.setproperty!(pd::PropertyDict, n::Symbol, v) = setindex!(pd, v, n) +Base.setproperty!(pd::PropertyDict, n::String, v) = setindex!(pd, v, n) + +Base.propertynames(pd::PropertyDict) = keys(pd) @static if isdefined(Base, :hasproperty) Base.hasproperty(pd::PropertyDict, key::Symbol) = haskey(pd, key) Base.hasproperty(pd::PropertyDict, key) = haskey(pd, key) end -Base.haskey(pd::PropertyDict, key) = haskey(getfield(pd, :d), key) -Base.haskey(pd::PropertyDict{<:AbstractString}, key::Symbol) = haskey(pd, String(key)) -Base.haskey(pd::PropertyDict{Symbol}, key::AbstractString) = haskey(pd, Symbol(key)) -function Base.haskey(pd::PropertyDict{Any}, key::AbstractString) - haskey(getfield(pd, :d), key) || haskey(getfield(pd, :d), Symbol(key)) -end -function Base.haskey(pd::PropertyDict{Any}, key::Symbol) - haskey(getfield(pd, :d), key) || haskey(getfield(pd, :d), String(key)) -end - -# a handful of dictionaries aren't just wrapped in `KeySet` and `ValueIterator` -Base.keys(pd::PropertyDict) = keys(getfield(pd, :d)) -Base.values(pd::PropertyDict) = values(getfield(pd, :d)) - -Base.propertynames(pd::PropertyDict) = keys(pd) end # module PropertyDicts diff --git a/test/runtests.jl b/test/runtests.jl index f5cb694..ef02e78 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -85,10 +85,6 @@ using Test @test length(pd) == length(d) end - @testset "string" begin - @test string(pd) == string(d) - end - push!(pd, :buz => 10) @test pop!(pd, :buz, 20) == 10 @test pop!(pd, :buz, 20) == 20 From a6836892720daf0f13738b42ba8d80ccb61104f3 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 27 Aug 2022 01:55:46 -0400 Subject: [PATCH 03/10] Update readme and version bump --- Project.toml | 2 +- README.md | 29 +++++++++++++++++++++++------ src/PropertyDicts.jl | 4 ++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index 0b2f53e..31d1ffa 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PropertyDicts" uuid = "f8a19df8-e894-5f55-a973-672c1158cbca" license = "MIT" -version = "0.1.2" +version = "0.2" [compat] julia = "1" diff --git a/README.md b/README.md index 9a7c5c1..f5f2ed3 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,30 @@ # PropertyDicts.jl -Wrap an `AbstractDict` to add `getproperty` support for `Symbol` and `AbstractString` keys. +Wrap an `AbstractDict` to add `getproperty` support for `Symbol` and `String` keys. ```julia -d = PropertyDict(Dict("foo"=>1, :bar=>2)) +julia> using PropertyDicts -d.foo, d.bar, d."foo" -> (1, 2, 1) +julia> d = PropertyDict(Dict("foo"=>1, :bar=>2)) +PropertyDict{Symbol, Int64, Dict{Symbol, Int64}} with 2 entries: + :bar => 2 + :foo => 1 + +julia> d.foo, d.bar, d."foo" +(1, 2, 1) + +julia> propertynames(d) +KeySet for a Dict{Symbol, Int64} with 2 entries. Keys: + :bar + :foo + +julia> d.baz = 3 +3 + +julia> d +PropertyDict{Symbol, Int64, Dict{Symbol, Int64}} with 3 entries: + :baz => 3 + :bar => 2 + :foo => 1 -d."bar" -> ERROR: KeyError: key "bar" not found ``` diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index 9311a50..7128cda 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -83,8 +83,8 @@ Base.setproperty!(pd::PropertyDict, n::String, v) = setindex!(pd, v, n) Base.propertynames(pd::PropertyDict) = keys(pd) @static if isdefined(Base, :hasproperty) - Base.hasproperty(pd::PropertyDict, key::Symbol) = haskey(pd, key) - Base.hasproperty(pd::PropertyDict, key) = haskey(pd, key) + Base.hasproperty(pd::PropertyDict, k::Symbol) = haskey(pd, _tokey(pd, k)) + Base.hasproperty(pd::PropertyDict, k) = haskey(pd, _tokey(pd, k)) end end # module PropertyDicts From 33b28462e56324b655b824be6087d0ec8340768d Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 27 Aug 2022 15:59:50 -0400 Subject: [PATCH 04/10] Support merge operations. --- src/PropertyDicts.jl | 123 ++++++++++++++++++++++++++++++++++++++++--- test/runtests.jl | 23 ++++++-- 2 files changed, 136 insertions(+), 10 deletions(-) diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index 7128cda..6371461 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -2,12 +2,13 @@ module PropertyDicts export PropertyDict -struct PropertyDict{K<:Union{String,Symbol}, V, T <: AbstractDict{K, V}} <: AbstractDict{K, V} - d::T +struct PropertyDict{K<:Union{String,Symbol}, V, D <: Union{AbstractDict,NamedTuple}} <: AbstractDict{K, V} + d::D PropertyDict(@nospecialize pd::PropertyDict) = pd PropertyDict(d::AbstractDict{String,V}) where {V} = new{String,V,typeof(d)}(d) PropertyDict(d::AbstractDict{Symbol,V}) where {V} = new{Symbol,V,typeof(d)}(d) + PropertyDict(nt::NamedTuple) = new{Symbol,eltype(nt),typeof(nt)}(nt) function PropertyDict(d::AbstractDict) dsym = Dict{Symbol,valtype(d)}() for (k,v) in d @@ -18,6 +19,8 @@ struct PropertyDict{K<:Union{String,Symbol}, V, T <: AbstractDict{K, V}} <: Abst PropertyDict(args...) = PropertyDict(Dict(args...)) end +Base.isempty(pd::PropertyDict) = isempty(getfield(pd, :d)) + Base.IteratorSize(@nospecialize T::Type{<:PropertyDict}) = Base.IteratorSize(fieldtype(T, :d)) Base.IteratorEltype(@nospecialize T::Type{<:PropertyDict}) = Base.IteratorEltype(eltype(T)) @@ -55,6 +58,9 @@ function Base.get!(f::Union{Function,Type}, pd::PropertyDict, k) get!(f, getfield(pd, :d), _tokey(pd, k)) end +Base.@propagate_inbounds function Base.getindex(pd::PropertyDict{Symbol,V,<:NamedTuple}, k::Symbol) where {V} + getfield(getfield(pd, :d), k) +end Base.@propagate_inbounds function Base.getindex(pd::PropertyDict, k) getindex(getfield(pd, :d), _tokey(pd, k)) end @@ -66,6 +72,22 @@ Base.haskey(pd::PropertyDict, k) = haskey(getfield(pd, :d), _tokey(pd, k)) Base.getkey(pd::PropertyDict, k, d) = getkey(getfield(pd, :d), _tokey(pd, k), d) +Base.reverse(pd::PropertyDict) = PropertyDict(getfield(pd, :d)) + +@inline function Base.iterate(pd::PropertyDict{Symbol,V,<:NamedTuple}) where {V} + if isempty(pd) + nothing + else + Pair{Symbol,V}(getfield(keys(pd), 1), getfield(getfield(pd, :d), 1)), 2 + end +end +@inline function Base.iterate(pd::PropertyDict{Symbol,V,<:NamedTuple}, s::Int) where {V} + if length(pd) < s + nothing + else + Pair{Symbol,V}(getfield(keys(getfield(pd, :d)), s), getfield(getfield(pd, :d), s)), s + 1 + end +end Base.iterate(pd::PropertyDict) = iterate(getfield(pd, :d)) Base.iterate(pd::PropertyDict, i) = iterate(getfield(pd, :d), i) @@ -74,17 +96,104 @@ Base.keys(pd::PropertyDict) = keys(getfield(pd, :d)) Base.values(pd::PropertyDict) = values(getfield(pd, :d)) ## property methods -Base.getproperty(pd::PropertyDict, n::Symbol) = getindex(pd, n) -Base.getproperty(pd::PropertyDict, n::String) = getindex(pd, n) +function Base.getproperty(pd::PropertyDict{Symbol,V,<:NamedTuple}, k::Symbol) where {V} + getfield(getfield(pd, :d), k) +end +Base.getproperty(pd::PropertyDict, k::Symbol) = getindex(pd, k) +Base.getproperty(pd::PropertyDict, k::String) = getindex(pd, k) -Base.setproperty!(pd::PropertyDict, n::Symbol, v) = setindex!(pd, v, n) -Base.setproperty!(pd::PropertyDict, n::String, v) = setindex!(pd, v, n) +Base.setproperty!(pd::PropertyDict, k::Symbol, v) = setindex!(pd, v, k) +Base.setproperty!(pd::PropertyDict, k::String, v) = setindex!(pd, v, k) -Base.propertynames(pd::PropertyDict) = keys(pd) +Base.propertynames(pd::PropertyDict) = keys(getfield(pd, :d)) @static if isdefined(Base, :hasproperty) Base.hasproperty(pd::PropertyDict, k::Symbol) = haskey(pd, _tokey(pd, k)) Base.hasproperty(pd::PropertyDict, k) = haskey(pd, _tokey(pd, k)) end +Base.copy(pd::PropertyDict{Symbol,<:Any,<:NamedTuple}) = pd +Base.copy(pd::PropertyDict) = PropertyDict(copy(getfield(pd, :d))) + +_promote_key(::Type{String}, ::Type{String}) = String +_promote_key(::Type{Symbol}, ::Type{Symbol}) = Symbol +_promote_key(::Type{Symbol}, ::Type{String}) = Symbol +_promote_key(::Type{String}, ::Type{Symbol}) = Symbol +_promote_keytypes(K::Union{Type{String},Type{Symbol}}) = K +function _promote_keytypes(K::Union{Type{String},Type{Symbol}}, d, ds...) + _promote_valtypes(_promote_key(K, keytype(d)), ds...) +end +_promote_valtypes(V) = V +function _promote_valtypes(V, d, ds...) # give up if promoted to any + V === Any ? Any : _promote_valtypes(promote_type(V, valtype(d)), ds...) +end + +## merge +Base.merge(pd::PropertyDict) = copy(pd) +function Base.merge(pd::PropertyDict, others::PropertyDict...) + K = _promote_keytypes(keytype(pd), others...) + V = _promote_valtypes(valtype(pd), others...) + out = PropertyDict(Dict{K,V}()) + for (k, v) in pairs(getfield(pd, :d)) + out[k] = v + end + merge!(out, others...) +end +function Base.merge(x::PropertyDict{Symbol,<:Any,<:NamedTuple}, y::PropertyDict{Symbol,<:Any,<:NamedTuple}, zs::PropertyDict{Symbol,<:Any,<:NamedTuple}...) + merge(merge(x, y), zs...) +end +function Base.merge(x::PropertyDict{Symbol,<:Any,<:NamedTuple}, y::PropertyDict{Symbol,<:Any,<:NamedTuple}) + PropertyDict(merge(getfield(x, :d), getfield(y, :d))) +end + +## mergewith +Base.mergewith(combine, pd::PropertyDict) = copy(pd) +function Base.mergewith(combine, pd::PropertyDict, others::PropertyDict...) + K = _promote_keytypes(keytype(pd), others...) + V = _promote_valtypes(valtype(pd), others...) + out = PropertyDict(Dict{K,promote_type(Core.Compiler.return_type(combine, Tuple{V,V}), V)}()) + for (k, v) in pd + out[k] = v + end + mergewith!(combine, out, others...) +end +function Base.mergewith(combine, x::PropertyDict{Symbol,<:Any,<:NamedTuple}, y::PropertyDict{Symbol,<:Any,<:NamedTuple}, zs::PropertyDict{Symbol,<:Any,<:NamedTuple}...) + _mergewith(combine, getfield(x, :d), getfield(y, :d)) +end +function Base.mergewith(combine, x::PropertyDict{Symbol,<:Any,<:NamedTuple}, y::PropertyDict{Symbol,<:Any,<:NamedTuple}) + _mergewith(combine, getfield(x, :d), getfield(y, :d)) +end +function _mergewith(combine, a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} + if @generated + names = Base.merge_names(an, bn) + t = Expr(:tuple) + for n in names + if Base.sym_in(n, an) + if Base.sym_in(n, bn) + push!(t.args, :(combine(getfield(a, $(QuoteNode(n))), getfield(b, $(QuoteNode(n)))))) + else + push!(t.args, :(getfield(a, $(QuoteNode(n))))) + end + else + push!(t.args, :(getfield(b, $(QuoteNode(n))))) + end + end + :(NamedTuple{$names}($(t))) + else + names = Base.merge_names(an, bn) + NamedTuple{names}(ntuple(Val{nfields(names)}()) do i + n = getfield(names, i) + if Base.sym_in(n, an) + if Base.sym_in(n, bn) + combine(getfield(a, n), getfield(b, n)) + else + getfield(a, n) + end + else + getfield(b, n) + end + end) + end +end + end # module PropertyDicts diff --git a/test/runtests.jl b/test/runtests.jl index ef02e78..c3cd65a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,10 +11,9 @@ using Test sym_props = PropertyDict(:foo => 1, :bar => 2) nt = (d =1, ) - pnt = pairs(nt) - ntpd = PropertyDict(pnt) + ntpd = PropertyDict(nt) @test keys(PropertyDict(ntpd)) === keys(nt) - @test values(PropertyDict(ntpd)) === values(pnt) + @test values(PropertyDict(ntpd)) === values(nt) @test propertynames(PropertyDict(ntpd)) === propertynames(nt) @test empty!(PropertyDict(Dict("foo"=>1, :bar=>2))) isa PropertyDict @@ -90,4 +89,22 @@ using Test @test pop!(pd, :buz, 20) == 20 @test sizehint!(pd, 5) === pd @test get(pd, delete!(pd, "foo"), 10) == 10 + + @testset "merge" begin + a = PropertyDict((a=1, b=2, c=3)) + b = PropertyDict((b=4, d=5)) + c = PropertyDict((a=1, b=2)) + d = PropertyDict((b=3, c=(d=1,))) + e = PropertyDict((c=(d=2,),)) + f = PropertyDict(Dict("foo"=>1, :bar=>2)) + + @test merge(a) === a + @test f !== merge(f) == f + @test merge(a, b) == PropertyDict((a = 1, b = 4, c = 3, d = 5)) + @test merge(c, d, e) == PropertyDict((a = 1, b = 3, c = (d = 2,))) + @test merge(a, f, c) == merge(f, a, c) + + combiner(x, y) = "$(x) and $(y)" + mergewith(combiner, a, f, c) + end end From 19b0ba236b70934062d0a3a874568a832e587e94 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 27 Aug 2022 16:30:41 -0400 Subject: [PATCH 05/10] add empty constructor --- src/PropertyDicts.jl | 3 ++- test/runtests.jl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index 6371461..ecc7cca 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -16,7 +16,8 @@ struct PropertyDict{K<:Union{String,Symbol}, V, D <: Union{AbstractDict,NamedTup end PropertyDict(dsym) end - PropertyDict(args...) = PropertyDict(Dict(args...)) + PropertyDict() = PropertyDict(NamedTuple()) + PropertyDict(arg, args...) = PropertyDict(Dict(arg, args...)) end Base.isempty(pd::PropertyDict) = isempty(getfield(pd, :d)) diff --git a/test/runtests.jl b/test/runtests.jl index c3cd65a..3baa2de 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -105,6 +105,6 @@ using Test @test merge(a, f, c) == merge(f, a, c) combiner(x, y) = "$(x) and $(y)" - mergewith(combiner, a, f, c) + mergewith(combiner, a, f, c, PropertyDict()) end end From fcd252e581df004f256db7516549240d161983cc Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sun, 28 Aug 2022 11:47:38 -0400 Subject: [PATCH 06/10] conditionally overload methods missing from base --- src/PropertyDicts.jl | 179 ++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 96 deletions(-) diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index ecc7cca..5b4f1d7 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -2,6 +2,27 @@ module PropertyDicts export PropertyDict +@static if !hasmethod(reverse, Tuple{NamedTuple}) + Base.reverse(nt::NamedTuple) = NamedTuple{reverse(keys(nt))}(reverse(values(nt))) +end +@static if !hasmethod(mergewith, Tuple{Any,NamedTuple,NamedTuple}) + function Base.mergewith(combine, a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} + names = Base.merge_names(an, bn) + NamedTuple{names}(ntuple(Val{nfields(names)}()) do i + n = getfield(names, i) + if Base.sym_in(n, an) + if Base.sym_in(n, bn) + combine(getfield(a, n), getfield(b, n)) + else + getfield(a, n) + end + else + getfield(b, n) + end + end) + end +end + struct PropertyDict{K<:Union{String,Symbol}, V, D <: Union{AbstractDict,NamedTuple}} <: AbstractDict{K, V} d::D @@ -20,7 +41,7 @@ struct PropertyDict{K<:Union{String,Symbol}, V, D <: Union{AbstractDict,NamedTup PropertyDict(arg, args...) = PropertyDict(Dict(arg, args...)) end -Base.isempty(pd::PropertyDict) = isempty(getfield(pd, :d)) +const NamedProperties{syms,T<:Tuple,V} = PropertyDict{Symbol,V,NamedTuple{syms,T}} Base.IteratorSize(@nospecialize T::Type{<:PropertyDict}) = Base.IteratorSize(fieldtype(T, :d)) Base.IteratorEltype(@nospecialize T::Type{<:PropertyDict}) = Base.IteratorEltype(eltype(T)) @@ -32,6 +53,9 @@ function Base.sizehint!(pd::PropertyDict, n::Integer) return pd end +Base.keytype(@nospecialize T::Type{<:PropertyDict{String}}) = String +Base.keytype(@nospecialize T::Type{<:PropertyDict{Symbol}}) = Symbol + _tokey(@nospecialize(pd::PropertyDict{String}), k::AbstractString) = k _tokey(@nospecialize(pd::PropertyDict{String}), k) = String(k) _tokey(@nospecialize(pd::PropertyDict{Symbol}), k::Symbol) = k @@ -44,22 +68,30 @@ function Base.empty!(pd::PropertyDict) empty!(getfield(pd, :d)) return pd end +Base.isempty(pd::PropertyDict) = isempty(getfield(pd, :d)) +function Base.empty(pd::PropertyDict, ::Type{K}=keytype(pd), ::Type{V}=valtype(pd)) where {K,V} + PropertyDict(empty(getfield(pd, :d), K, V)) +end +Base.empty(pd::NamedProperties, ::Type{Symbol}, ::Type{Union{}}) = PropertyDict() + function Base.delete!(pd::PropertyDict, k) delete!(getfield(pd, :d), _tokey(pd, k)) return pd end -Base.empty(pd::PropertyDict) = PropertyDict(empty(getfield(pd, :d))) -Base.get(pd::PropertyDict, k, d) = get(getfield(pd, :d), _tokey(pd, k), d) +function Base.get(pd::PropertyDict, k, d) + get(getfield(pd, :d), _tokey(pd, k), d) +end function Base.get(f::Union{Function,Type}, pd::PropertyDict, k) get(f, getfield(pd, :d), _tokey(pd, k)) end -Base.get!(pd::PropertyDict, k, d) = get!(getfield(pd, :d), _tokey(pd, k), d) +function Base.get!(pd::PropertyDict, k, d) + get!(getfield(pd, :d), _tokey(pd, k), d) +end function Base.get!(f::Union{Function,Type}, pd::PropertyDict, k) get!(f, getfield(pd, :d), _tokey(pd, k)) end - -Base.@propagate_inbounds function Base.getindex(pd::PropertyDict{Symbol,V,<:NamedTuple}, k::Symbol) where {V} +Base.@propagate_inbounds function Base.getindex(pd::NamedProperties, k::Symbol) getfield(getfield(pd, :d), k) end Base.@propagate_inbounds function Base.getindex(pd::PropertyDict, k) @@ -69,132 +101,87 @@ Base.@propagate_inbounds function Base.setindex!(pd::PropertyDict, v, k) setindex!(getfield(pd, :d), v, _tokey(pd, k)) end -Base.haskey(pd::PropertyDict, k) = haskey(getfield(pd, :d), _tokey(pd, k)) - -Base.getkey(pd::PropertyDict, k, d) = getkey(getfield(pd, :d), _tokey(pd, k), d) - -Base.reverse(pd::PropertyDict) = PropertyDict(getfield(pd, :d)) +Base.reverse(pd::PropertyDict) = PropertyDict(reverse(getfield(pd, :d))) -@inline function Base.iterate(pd::PropertyDict{Symbol,V,<:NamedTuple}) where {V} +@inline function Base.iterate(pd::NamedProperties) if isempty(pd) nothing else - Pair{Symbol,V}(getfield(keys(pd), 1), getfield(getfield(pd, :d), 1)), 2 + Pair{Symbol,valtype(pd)}(getfield(keys(pd), 1), getfield(getfield(pd, :d), 1)), 2 end end -@inline function Base.iterate(pd::PropertyDict{Symbol,V,<:NamedTuple}, s::Int) where {V} +@inline function Base.iterate(pd::NamedProperties, s::Int) where {V} if length(pd) < s nothing else - Pair{Symbol,V}(getfield(keys(getfield(pd, :d)), s), getfield(getfield(pd, :d), s)), s + 1 + Pair{Symbol,valtype(pd)}(getfield(keys(getfield(pd, :d)), s), getfield(getfield(pd, :d), s)), s + 1 end end Base.iterate(pd::PropertyDict) = iterate(getfield(pd, :d)) Base.iterate(pd::PropertyDict, i) = iterate(getfield(pd, :d), i) -# a handful of dictionaries aren't just wrapped in `KeySet` and `ValueIterator` -Base.keys(pd::PropertyDict) = keys(getfield(pd, :d)) Base.values(pd::PropertyDict) = values(getfield(pd, :d)) -## property methods -function Base.getproperty(pd::PropertyDict{Symbol,V,<:NamedTuple}, k::Symbol) where {V} - getfield(getfield(pd, :d), k) +Base.haskey(pd::PropertyDict, k) = haskey(getfield(pd, :d), _tokey(pd, k)) +Base.getkey(pd::PropertyDict, k, d) = getkey(getfield(pd, :d), _tokey(pd, k), d) +Base.keys(pd::PropertyDict) = keys(getfield(pd, :d)) + +@static if isdefined(Base, :hasproperty) + Base.hasproperty(pd::PropertyDict, k::Symbol) = haskey(pd, _tokey(pd, k)) + Base.hasproperty(pd::PropertyDict, k::AbstractString) = haskey(pd, _tokey(pd, k)) end +Base.propertynames(pd::PropertyDict) = keys(getfield(pd, :d)) +Base.getproperty(pd::NamedProperties, k::Symbol) = getfield(getfield(pd, :d), k) Base.getproperty(pd::PropertyDict, k::Symbol) = getindex(pd, k) Base.getproperty(pd::PropertyDict, k::String) = getindex(pd, k) - Base.setproperty!(pd::PropertyDict, k::Symbol, v) = setindex!(pd, v, k) Base.setproperty!(pd::PropertyDict, k::String, v) = setindex!(pd, v, k) -Base.propertynames(pd::PropertyDict) = keys(getfield(pd, :d)) - -@static if isdefined(Base, :hasproperty) - Base.hasproperty(pd::PropertyDict, k::Symbol) = haskey(pd, _tokey(pd, k)) - Base.hasproperty(pd::PropertyDict, k) = haskey(pd, _tokey(pd, k)) -end - -Base.copy(pd::PropertyDict{Symbol,<:Any,<:NamedTuple}) = pd +Base.copy(pd::NamedProperties) = pd Base.copy(pd::PropertyDict) = PropertyDict(copy(getfield(pd, :d))) -_promote_key(::Type{String}, ::Type{String}) = String -_promote_key(::Type{Symbol}, ::Type{Symbol}) = Symbol -_promote_key(::Type{Symbol}, ::Type{String}) = Symbol -_promote_key(::Type{String}, ::Type{Symbol}) = Symbol -_promote_keytypes(K::Union{Type{String},Type{Symbol}}) = K -function _promote_keytypes(K::Union{Type{String},Type{Symbol}}, d, ds...) - _promote_valtypes(_promote_key(K, keytype(d)), ds...) -end -_promote_valtypes(V) = V -function _promote_valtypes(V, d, ds...) # give up if promoted to any - V === Any ? Any : _promote_valtypes(promote_type(V, valtype(d)), ds...) -end - -## merge +## merge and mergewith Base.merge(pd::PropertyDict) = copy(pd) -function Base.merge(pd::PropertyDict, others::PropertyDict...) - K = _promote_keytypes(keytype(pd), others...) - V = _promote_valtypes(valtype(pd), others...) +Base.merge(pd::NamedProperties, pds::NamedProperties...) = _mergeprops(_getarg2, pd, pds...) +_getarg2(@nospecialize(arg1), @nospecialize(arg2)) = arg2 +function Base.merge(pd::PropertyDict, pds::PropertyDict...) + K = _promote_keytypes((pd, pds...)) + V = _promote_valtypes(valtype(pd), pds...) out = PropertyDict(Dict{K,V}()) - for (k, v) in pairs(getfield(pd, :d)) + for (k,v) in pd out[k] = v end - merge!(out, others...) -end -function Base.merge(x::PropertyDict{Symbol,<:Any,<:NamedTuple}, y::PropertyDict{Symbol,<:Any,<:NamedTuple}, zs::PropertyDict{Symbol,<:Any,<:NamedTuple}...) - merge(merge(x, y), zs...) -end -function Base.merge(x::PropertyDict{Symbol,<:Any,<:NamedTuple}, y::PropertyDict{Symbol,<:Any,<:NamedTuple}) - PropertyDict(merge(getfield(x, :d), getfield(y, :d))) + merge!(out, pds...) end -## mergewith Base.mergewith(combine, pd::PropertyDict) = copy(pd) -function Base.mergewith(combine, pd::PropertyDict, others::PropertyDict...) - K = _promote_keytypes(keytype(pd), others...) - V = _promote_valtypes(valtype(pd), others...) - out = PropertyDict(Dict{K,promote_type(Core.Compiler.return_type(combine, Tuple{V,V}), V)}()) - for (k, v) in pd +function Base.mergewith(combine, pd::PropertyDict, pds::PropertyDict...) + K = _promote_keytypes((pd, pds...)) + V0 = _promote_valtypes(valtype(pd), pds...) + V = promote_type(Core.Compiler.return_type(combine, Tuple{V0,V0}), V0) + out = PropertyDict(Dict{K,V}()) + for (k,v) in pd out[k] = v end - mergewith!(combine, out, others...) + mergewith!(combine, out, pds...) end -function Base.mergewith(combine, x::PropertyDict{Symbol,<:Any,<:NamedTuple}, y::PropertyDict{Symbol,<:Any,<:NamedTuple}, zs::PropertyDict{Symbol,<:Any,<:NamedTuple}...) - _mergewith(combine, getfield(x, :d), getfield(y, :d)) +@inline function Base.mergewith(combine, pd::NamedProperties, pds::NamedProperties...) + _mergeprops(combine, pd, pds...) end -function Base.mergewith(combine, x::PropertyDict{Symbol,<:Any,<:NamedTuple}, y::PropertyDict{Symbol,<:Any,<:NamedTuple}) - _mergewith(combine, getfield(x, :d), getfield(y, :d)) +_mergeprops(combine, @nospecialize(x::NamedProperties)) = x +@inline function _mergeprops(combine, x::NamedProperties, y::NamedProperties) + PropertyDict(mergewith(combine, getfield(x, :d), getfield(y, :d))) end -function _mergewith(combine, a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} - if @generated - names = Base.merge_names(an, bn) - t = Expr(:tuple) - for n in names - if Base.sym_in(n, an) - if Base.sym_in(n, bn) - push!(t.args, :(combine(getfield(a, $(QuoteNode(n))), getfield(b, $(QuoteNode(n)))))) - else - push!(t.args, :(getfield(a, $(QuoteNode(n))))) - end - else - push!(t.args, :(getfield(b, $(QuoteNode(n))))) - end - end - :(NamedTuple{$names}($(t))) - else - names = Base.merge_names(an, bn) - NamedTuple{names}(ntuple(Val{nfields(names)}()) do i - n = getfield(names, i) - if Base.sym_in(n, an) - if Base.sym_in(n, bn) - combine(getfield(a, n), getfield(b, n)) - else - getfield(a, n) - end - else - getfield(b, n) - end - end) - end +@inline function _mergeprops(combine, x::NamedProperties, y::NamedProperties, zs::NamedProperties...) + _mergeprops(combine, _mergeprops(combine, x, y), zs...) +end + +# fall back to Symbol if we don't clearly have String +_promote_keytypes(@nospecialize(pds::Tuple{Vararg{PropertyDict{String}}})) = String +_promote_keytypes(@nospecialize(pds::Tuple{Vararg{PropertyDict}})) = Symbol +_promote_valtypes(V) = V +function _promote_valtypes(V, d, ds...) # give up if promoted to any + V === Any ? Any : _promote_valtypes(promote_type(V, valtype(d)), ds...) end end # module PropertyDicts From 125a5183a1e66a74e46069d1fd0e905a108b144f Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sun, 28 Aug 2022 12:11:08 -0400 Subject: [PATCH 07/10] Use latest LTS --- .github/workflows/CI.yml | 2 +- Project.toml | 2 +- src/PropertyDicts.jl | 6 ++---- test/runtests.jl | 39 +++++++++++++++++++++++---------------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c0447e2..860e22a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: version: - - '1.0' + - '1.6' - '1' os: - ubuntu-latest diff --git a/Project.toml b/Project.toml index 31d1ffa..6ae5064 100644 --- a/Project.toml +++ b/Project.toml @@ -4,7 +4,7 @@ license = "MIT" version = "0.2" [compat] -julia = "1" +julia = "1.6" [extras] OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index 5b4f1d7..e04b113 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -126,10 +126,8 @@ Base.haskey(pd::PropertyDict, k) = haskey(getfield(pd, :d), _tokey(pd, k)) Base.getkey(pd::PropertyDict, k, d) = getkey(getfield(pd, :d), _tokey(pd, k), d) Base.keys(pd::PropertyDict) = keys(getfield(pd, :d)) -@static if isdefined(Base, :hasproperty) - Base.hasproperty(pd::PropertyDict, k::Symbol) = haskey(pd, _tokey(pd, k)) - Base.hasproperty(pd::PropertyDict, k::AbstractString) = haskey(pd, _tokey(pd, k)) -end +Base.hasproperty(pd::PropertyDict, k::Symbol) = haskey(pd, _tokey(pd, k)) +Base.hasproperty(pd::PropertyDict, k::AbstractString) = haskey(pd, _tokey(pd, k)) Base.propertynames(pd::PropertyDict) = keys(getfield(pd, :d)) Base.getproperty(pd::NamedProperties, k::Symbol) = getfield(getfield(pd, :d), k) Base.getproperty(pd::PropertyDict, k::Symbol) = getindex(pd, k) diff --git a/test/runtests.jl b/test/runtests.jl index 3baa2de..97a6275 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,17 +12,23 @@ using Test nt = (d =1, ) ntpd = PropertyDict(nt) - @test keys(PropertyDict(ntpd)) === keys(nt) + + @test length(pd) == length(d) + @test values(PropertyDict(ntpd)) === values(nt) - @test propertynames(PropertyDict(ntpd)) === propertynames(nt) + + @test empty!(PropertyDict(Dict("foo"=>1, :bar=>2))) isa PropertyDict + @test empty(pd) == PropertyDict(empty(d)) - if isdefined(Base, :hasproperty) - @test hasproperty(sym_props, "bar") - @test hasproperty(str_props, :bar) - @test hasproperty(pd, :foo) - @test hasproperty(pd, "bar") - end + @test propertynames(PropertyDict(ntpd)) === propertynames(nt) + + @test keys(PropertyDict(ntpd)) === keys(nt) + @test hasproperty(sym_props, "bar") + @test hasproperty(str_props, :bar) + @test hasproperty(pd, :foo) + @test hasproperty(pd, "bar") + @test haskey(pd, :foo) @testset "convert" begin expected = OrderedDict @@ -80,9 +86,7 @@ using Test @test Base.IteratorEltype(pd) == Base.IteratorEltype(d) end - @testset "length" begin - @test length(pd) == length(d) - end + @test reverse(PropertyDict((a=1, b=2, c=3))) === PropertyDict(reverse((a=1, b=2, c=3))) push!(pd, :buz => 10) @test pop!(pd, :buz, 20) == 10 @@ -90,21 +94,24 @@ using Test @test sizehint!(pd, 5) === pd @test get(pd, delete!(pd, "foo"), 10) == 10 - @testset "merge" begin + @testset "merge & mergewith" begin a = PropertyDict((a=1, b=2, c=3)) b = PropertyDict((b=4, d=5)) c = PropertyDict((a=1, b=2)) d = PropertyDict((b=3, c=(d=1,))) e = PropertyDict((c=(d=2,),)) - f = PropertyDict(Dict("foo"=>1, :bar=>2)) + f = PropertyDict(Dict("foo"=>1, "bar"=>2)) @test merge(a) === a @test f !== merge(f) == f - @test merge(a, b) == PropertyDict((a = 1, b = 4, c = 3, d = 5)) - @test merge(c, d, e) == PropertyDict((a = 1, b = 3, c = (d = 2,))) + @test @inferred(merge(a, b)) == PropertyDict((a = 1, b = 4, c = 3, d = 5)) + @test @inferred(merge(c, d, e)) == PropertyDict((a = 1, b = 3, c = (d = 2,))) @test merge(a, f, c) == merge(f, a, c) combiner(x, y) = "$(x) and $(y)" - mergewith(combiner, a, f, c, PropertyDict()) + @test mergewith(combiner, a, f, c, PropertyDict()) == + PropertyDict(:a=>"1 and 1", :b=>"2 and 2", :c=>3, :bar=>2, :foo=>1) + @test @inferred(mergewith(combiner, a, b, c, PropertyDict())) == + PropertyDict((a = "1 and 1", b = "2 and 4 and 2", c = 3, d = 5)) end end From fb1666dae24e1db06fe941571fba1c184cd8d9bf Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sun, 28 Aug 2022 12:20:58 -0400 Subject: [PATCH 08/10] increase test coverage --- src/PropertyDicts.jl | 2 +- test/runtests.jl | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index e04b113..497e14e 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -37,8 +37,8 @@ struct PropertyDict{K<:Union{String,Symbol}, V, D <: Union{AbstractDict,NamedTup end PropertyDict(dsym) end - PropertyDict() = PropertyDict(NamedTuple()) PropertyDict(arg, args...) = PropertyDict(Dict(arg, args...)) + PropertyDict(; kwargs...) = PropertyDict(values(kwargs)) end const NamedProperties{syms,T<:Tuple,V} = PropertyDict{Symbol,V,NamedTuple{syms,T}} diff --git a/test/runtests.jl b/test/runtests.jl index 97a6275..d0ec0e8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -23,12 +23,16 @@ using Test @test propertynames(PropertyDict(ntpd)) === propertynames(nt) + @test ntpd.d === nt.d @test keys(PropertyDict(ntpd)) === keys(nt) @test hasproperty(sym_props, "bar") + @test keytype(str_props) <: String + @test keytype(sym_props) <: Symbol @test hasproperty(str_props, :bar) @test hasproperty(pd, :foo) @test hasproperty(pd, "bar") @test haskey(pd, :foo) + @test getkey(pd, :buz, nothing) === nothing @testset "convert" begin expected = OrderedDict @@ -108,6 +112,7 @@ using Test @test @inferred(merge(c, d, e)) == PropertyDict((a = 1, b = 3, c = (d = 2,))) @test merge(a, f, c) == merge(f, a, c) + @test mergewith(+, a, b) == PropertyDict(a=1, b=6, c=3, d=5) combiner(x, y) = "$(x) and $(y)" @test mergewith(combiner, a, f, c, PropertyDict()) == PropertyDict(:a=>"1 and 1", :b=>"2 and 2", :c=>3, :bar=>2, :foo=>1) From e29a5a3cbf4cf6207be9d26e25b5c661cbe46f62 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sun, 28 Aug 2022 14:17:17 -0400 Subject: [PATCH 09/10] test methods with NamedTuple's --- test/runtests.jl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index d0ec0e8..866bb9c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -93,11 +93,18 @@ using Test @test reverse(PropertyDict((a=1, b=2, c=3))) === PropertyDict(reverse((a=1, b=2, c=3))) push!(pd, :buz => 10) - @test pop!(pd, :buz, 20) == 10 + @test pop!(pd, :buz) == 10 @test pop!(pd, :buz, 20) == 20 @test sizehint!(pd, 5) === pd @test get(pd, delete!(pd, "foo"), 10) == 10 + @testset "NamedProperties" begin + pd = PropertyDict(x=1) + @test copy(pd) == pd + @test empty(pd) === PropertyDict() + @test pd[:x] == 1 + end + @testset "merge & mergewith" begin a = PropertyDict((a=1, b=2, c=3)) b = PropertyDict((b=4, d=5)) @@ -112,6 +119,8 @@ using Test @test @inferred(merge(c, d, e)) == PropertyDict((a = 1, b = 3, c = (d = 2,))) @test merge(a, f, c) == merge(f, a, c) + @test mergewith(+, a) == a + @test mergewith(+, f, f) == PropertyDict(Dict("foo"=>2, "bar"=>4)) @test mergewith(+, a, b) == PropertyDict(a=1, b=6, c=3, d=5) combiner(x, y) = "$(x) and $(y)" @test mergewith(combiner, a, f, c, PropertyDict()) == From b0fe6e993e7978d7fc665c5a7dbe9c5a692161d6 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sun, 28 Aug 2022 14:17:26 -0400 Subject: [PATCH 10/10] this shouldn't have specific type args in the method head --- src/PropertyDicts.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PropertyDicts.jl b/src/PropertyDicts.jl index 497e14e..d427314 100644 --- a/src/PropertyDicts.jl +++ b/src/PropertyDicts.jl @@ -72,7 +72,7 @@ Base.isempty(pd::PropertyDict) = isempty(getfield(pd, :d)) function Base.empty(pd::PropertyDict, ::Type{K}=keytype(pd), ::Type{V}=valtype(pd)) where {K,V} PropertyDict(empty(getfield(pd, :d), K, V)) end -Base.empty(pd::NamedProperties, ::Type{Symbol}, ::Type{Union{}}) = PropertyDict() +Base.empty(pd::NamedProperties, ::Type{K}, ::Type{V}) where {K,V} = PropertyDict() function Base.delete!(pd::PropertyDict, k) delete!(getfield(pd, :d), _tokey(pd, k))