Skip to content

Commit

Permalink
Merge pull request #8 from JuliaCollections/more_dict_interface
Browse files Browse the repository at this point in the history
More AbstractDict interface and reduce invalidations/ambiguities
  • Loading branch information
Tokazama authored Aug 28, 2022
2 parents 9fb29c6 + b0fe6e9 commit 4f28c32
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.0'
- '1.6'
- '1'
os:
- ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name = "PropertyDicts"
uuid = "f8a19df8-e894-5f55-a973-672c1158cbca"
license = "MIT"
version = "0.1.2"
version = "0.2"

[compat]
julia = "1"
julia = "1.6"

[extras]
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Expand Down
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
```
240 changes: 153 additions & 87 deletions src/PropertyDicts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,118 +2,184 @@ module PropertyDicts

export PropertyDict

struct PropertyDict{K, V, T <: AbstractDict{K, V}} <: AbstractDict{K, V}
d::T
@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

PropertyDict(d::T) where {T <: AbstractDict} = new{keytype(d), valtype(d), T}(d)
PropertyDict(args...) = PropertyDict(Dict(args...))
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
dsym[Symbol(k)] = v
end
PropertyDict(dsym)
end
PropertyDict(arg, args...) = PropertyDict(Dict(arg, args...))
PropertyDict(; kwargs...) = PropertyDict(values(kwargs))
end

unwrap(d::PropertyDict) = getfield(d, :d)
const NamedProperties{syms,T<:Tuple,V} = PropertyDict{Symbol,V,NamedTuple{syms,T}}

function Base.sizehint!(d::PropertyDict, n::Integer)
sizehint!(unwrap(d), n)
return 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!(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.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
_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!(d::PropertyDict, key)
delete!(unwrap(d), key)
return d
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{K}, ::Type{V}) where {K,V} = PropertyDict()

Base.getproperty(d::PropertyDict, n::Symbol) = getindex(d, n)
Base.getproperty(d::PropertyDict, n::String) = getindex(d, n)
function Base.delete!(pd::PropertyDict, k)
delete!(getfield(pd, :d), _tokey(pd, k))
return pd
end

Base.setproperty!(d::PropertyDict, n::Symbol, v) = setindex!(d, v, n)
Base.setproperty!(d::PropertyDict, n::String, v) = setindex!(d, v, n)
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
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::NamedProperties, k::Symbol)
getfield(getfield(pd, :d), k)
end
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.reverse(pd::PropertyDict) = PropertyDict(reverse(getfield(pd, :d)))

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)
if out === Base.secret_table_token
return get(unwrap(d), String(k), default)
@inline function Base.iterate(pd::NamedProperties)
if isempty(pd)
nothing
else
return out
Pair{Symbol,valtype(pd)}(getfield(keys(pd), 1), getfield(getfield(pd, :d), 1)), 2
end
end
function Base.get(d::PropertyDict{Any}, k::String, default)
out = get(unwrap(d), k, Base.secret_table_token)
if out === Base.secret_table_token
return get(unwrap(d), Symbol(k), default)
@inline function Base.iterate(pd::NamedProperties, s::Int) where {V}
if length(pd) < s
nothing
else
return out
Pair{Symbol,valtype(pd)}(getfield(keys(getfield(pd, :d)), s), getfield(getfield(pd, :d), s)), s + 1
end
end

function Base.get(f::Union{Function,Type}, d::PropertyDict, k)
out = get(d, k, Base.secret_table_token)
if out === Base.secret_table_token
return f()
else
return out
Base.iterate(pd::PropertyDict) = iterate(getfield(pd, :d))
Base.iterate(pd::PropertyDict, i) = iterate(getfield(pd, :d), i)

Base.values(pd::PropertyDict) = values(getfield(pd, :d))

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

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)
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.copy(pd::NamedProperties) = pd
Base.copy(pd::PropertyDict) = PropertyDict(copy(getfield(pd, :d)))

## merge and mergewith
Base.merge(pd::PropertyDict) = copy(pd)
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 pd
out[k] = v
end
merge!(out, pds...)
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)
if out === Base.secret_table_token
default = f()
setindex!(d, default, k)
return default
else
return out
Base.mergewith(combine, pd::PropertyDict) = copy(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, pds...)
end

function Base.getindex(d::PropertyDict, k)
out = get(d, k, Base.secret_table_token)
out === Base.secret_table_token && throw(KeyError(k))
return out
@inline function Base.mergewith(combine, pd::NamedProperties, pds::NamedProperties...)
_mergeprops(combine, pd, pds...)
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.iterate(d::PropertyDict) = iterate(unwrap(d))
Base.iterate(d::PropertyDict, i) = iterate(unwrap(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.string(d::PropertyDict) = string(unwrap(d))

@static if isdefined(Base, :hasproperty)
Base.hasproperty(d::PropertyDict, key::Symbol) = haskey(d, key)
Base.hasproperty(d::PropertyDict, key) = haskey(d, key)
_mergeprops(combine, @nospecialize(x::NamedProperties)) = x
@inline function _mergeprops(combine, x::NamedProperties, y::NamedProperties)
PropertyDict(mergewith(combine, getfield(x, :d), getfield(y, :d)))
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))
@inline function _mergeprops(combine, x::NamedProperties, y::NamedProperties, zs::NamedProperties...)
_mergeprops(combine, _mergeprops(combine, x, y), zs...)
end
function Base.haskey(d::PropertyDict{Any}, key::Symbol)
haskey(unwrap(d), key) || haskey(unwrap(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.propertynames(d::PropertyDict) = keys(d)
# 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
Loading

2 comments on commit 4f28c32

@Tokazama
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/67240

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.0 -m "<description of version>" 4f28c324884987ba8173a7a4565d9b65d512593c
git push origin v0.2.0

Please sign in to comment.