Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: IdDict{K,V} as wrapped ObjectIdDict #25196

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions base/abstractdict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ eltype(::Type{AbstractDict{K,V}}) where {K,V} = Pair{K,V}

function isequal(l::AbstractDict, r::AbstractDict)
l === r && return true
if isa(l,ObjectIdDict) != isa(r,ObjectIdDict)
if isa(l,IdDict) != isa(r,IdDict) || isa(l,_ObjectIdDict) != isa(r,_ObjectIdDict)
return false
end
if length(l) != length(r) return false end
Expand All @@ -464,7 +464,7 @@ end

function ==(l::AbstractDict, r::AbstractDict)
l === r && return true
if isa(l,ObjectIdDict) != isa(r,ObjectIdDict)
if isa(l,IdDict) != isa(r,IdDict) || isa(l,_ObjectIdDict) != isa(r,_ObjectIdDict)
return false
end
if length(l) != length(r) return false end
Expand Down Expand Up @@ -505,42 +505,44 @@ push!(t::AbstractDict, p::Pair, q::Pair, r::Pair...) = push!(push!(push!(t, p),
# hashing objects by identity

"""
ObjectIdDict([itr])
_ObjectIdDict([itr])

`ObjectIdDict()` constructs a hash table where the keys are (always)
Internally used object-id dictionary, use [`IdDict`](@ref) instead.

`_ObjectIdDict()` constructs a hash table where the keys are (always)
object identities. Unlike `Dict` it is not parameterized on its key
and value type and thus its `eltype` is always `Pair{Any,Any}`.

See [`Dict`](@ref) for further help.
"""
mutable struct ObjectIdDict <: AbstractDict{Any,Any}
mutable struct _ObjectIdDict <: AbstractDict{Any,Any}
ht::Vector{Any}
ndel::Int
ObjectIdDict() = new(Vector{Any}(uninitialized, 32), 0)
_ObjectIdDict() = new(Vector{Any}(uninitialized, 32), 0)

function ObjectIdDict(itr)
d = ObjectIdDict()
function _ObjectIdDict(itr)
d = _ObjectIdDict()
for (k,v) in itr; d[k] = v; end
d
end

function ObjectIdDict(pairs::Pair...)
d = ObjectIdDict()
function _ObjectIdDict(pairs::Pair...)
d = _ObjectIdDict()
for (k,v) in pairs; d[k] = v; end
d
end

ObjectIdDict(o::ObjectIdDict) = new(copy(o.ht))
_ObjectIdDict(o::_ObjectIdDict) = new(copy(o.ht))
end

empty(d::ObjectIdDict, ::Type{Any}, ::Type{Any}) = ObjectIdDict()
empty(d::_ObjectIdDict, ::Type{Any}, ::Type{Any}) = _ObjectIdDict()

function rehash!(t::ObjectIdDict, newsz = length(t.ht))
function rehash!(t::_ObjectIdDict, newsz = length(t.ht))
t.ht = ccall(:jl_idtable_rehash, Any, (Any, Csize_t), t.ht, newsz)
t
end

function sizehint!(t::ObjectIdDict, newsz)
function sizehint!(t::_ObjectIdDict, newsz)
newsz = _tablesz(newsz*2) # *2 for keys and values in same array
oldsz = length(t.ht)
# grow at least 25%
Expand All @@ -550,7 +552,7 @@ function sizehint!(t::ObjectIdDict, newsz)
rehash!(t, newsz)
end

function setindex!(t::ObjectIdDict, @nospecialize(v), @nospecialize(k))
function setindex!(t::_ObjectIdDict, @nospecialize(v), @nospecialize(k))
if t.ndel >= ((3*length(t.ht))>>2)
rehash!(t, max(length(t.ht)>>1, 32))
t.ndel = 0
Expand All @@ -559,27 +561,27 @@ function setindex!(t::ObjectIdDict, @nospecialize(v), @nospecialize(k))
return t
end

get(t::ObjectIdDict, @nospecialize(key), @nospecialize(default)) =
get(t::_ObjectIdDict, @nospecialize(key), @nospecialize(default)) =
ccall(:jl_eqtable_get, Any, (Any, Any, Any), t.ht, key, default)

function pop!(t::ObjectIdDict, @nospecialize(key), @nospecialize(default))
function pop!(t::_ObjectIdDict, @nospecialize(key), @nospecialize(default))
val = ccall(:jl_eqtable_pop, Any, (Any, Any, Any), t.ht, key, default)
# TODO: this can underestimate `ndel`
val === default || (t.ndel += 1)
return val
end

function pop!(t::ObjectIdDict, @nospecialize(key))
function pop!(t::_ObjectIdDict, @nospecialize(key))
val = pop!(t, key, secret_table_token)
val !== secret_table_token ? val : throw(KeyError(key))
end

function delete!(t::ObjectIdDict, @nospecialize(key))
function delete!(t::_ObjectIdDict, @nospecialize(key))
pop!(t, key, secret_table_token)
t
end

function empty!(t::ObjectIdDict)
function empty!(t::_ObjectIdDict)
resize!(t.ht, 32)
ccall(:memset, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), t.ht, 0, sizeof(t.ht))
t.ndel = 0
Expand All @@ -588,22 +590,22 @@ end

_oidd_nextind(a, i) = reinterpret(Int,ccall(:jl_eqtable_nextind, Csize_t, (Any, Csize_t), a, i))

start(t::ObjectIdDict) = _oidd_nextind(t.ht, 0)
done(t::ObjectIdDict, i) = (i == -1)
next(t::ObjectIdDict, i) = (Pair{Any,Any}(t.ht[i+1],t.ht[i+2]), _oidd_nextind(t.ht, i+2))
start(t::_ObjectIdDict) = _oidd_nextind(t.ht, 0)
done(t::_ObjectIdDict, i) = (i == -1)
next(t::_ObjectIdDict, i) = (Pair{Any,Any}(t.ht[i+1],t.ht[i+2]), _oidd_nextind(t.ht, i+2))

function length(d::ObjectIdDict)
function length(d::_ObjectIdDict)
n = 0
for pair in d
n+=1
end
n
end

copy(o::ObjectIdDict) = ObjectIdDict(o)
copy(o::_ObjectIdDict) = _ObjectIdDict(o)

get!(o::ObjectIdDict, key, default) = (o[key] = get(o, key, default))
get!(o::_ObjectIdDict, key, default) = (o[key] = get(o, key, default))

# For some AbstractDict types, it is safe to implement filter!
# by deleting keys during iteration.
filter!(f, d::ObjectIdDict) = filter_in_one_pass!(f, d)
filter!(f, d::_ObjectIdDict) = filter_in_one_pass!(f, d)
2 changes: 1 addition & 1 deletion base/codevalidation.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

# Expr head => argument count bounds
const VALID_EXPR_HEADS = ObjectIdDict(
const VALID_EXPR_HEADS = _ObjectIdDict(
:call => 1:typemax(Int),
:invoke => 2:typemax(Int),
:static_parameter => 1:1,
Expand Down
25 changes: 12 additions & 13 deletions base/deepcopy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# deep copying

# Note: deepcopy_internal(::Any, ::ObjectIdDict) is
# Note: deepcopy_internal(::Any, ::IdDict) is
# only exposed for specialization by libraries

"""
Expand All @@ -19,21 +19,21 @@ i.e. functions which may contain hidden internal references.

While it isn't normally necessary, user-defined types can override the default `deepcopy`
behavior by defining a specialized version of the function
`deepcopy_internal(x::T, dict::ObjectIdDict)` (which shouldn't otherwise be used),
`deepcopy_internal(x::T, dict::IdDict)` (which shouldn't otherwise be used),
where `T` is the type to be specialized for, and `dict` keeps track of objects copied
so far within the recursion. Within the definition, `deepcopy_internal` should be used
in place of `deepcopy`, and the `dict` variable should be
updated as appropriate before returning.
"""
deepcopy(x) = deepcopy_internal(x, ObjectIdDict())::typeof(x)
deepcopy(x) = deepcopy_internal(x, IdDict())::typeof(x)

deepcopy_internal(x::Union{Symbol,Core.MethodInstance,Method,GlobalRef,DataType,Union,Task},
stackdict::ObjectIdDict) = x
deepcopy_internal(x::Tuple, stackdict::ObjectIdDict) =
stackdict::IdDict) = x
deepcopy_internal(x::Tuple, stackdict::IdDict) =
ntuple(i->deepcopy_internal(x[i], stackdict), length(x))
deepcopy_internal(x::Module, stackdict::ObjectIdDict) = error("deepcopy of Modules not supported")
deepcopy_internal(x::Module, stackdict::IdDict) = error("deepcopy of Modules not supported")

function deepcopy_internal(x::SimpleVector, stackdict::ObjectIdDict)
function deepcopy_internal(x::SimpleVector, stackdict::IdDict)
if haskey(stackdict, x)
return stackdict[x]
end
Expand All @@ -42,7 +42,7 @@ function deepcopy_internal(x::SimpleVector, stackdict::ObjectIdDict)
return y
end

function deepcopy_internal(x::String, stackdict::ObjectIdDict)
function deepcopy_internal(x::String, stackdict::IdDict)
if haskey(stackdict, x)
return stackdict[x]
end
Expand All @@ -51,7 +51,7 @@ function deepcopy_internal(x::String, stackdict::ObjectIdDict)
return y
end

function deepcopy_internal(@nospecialize(x), stackdict::ObjectIdDict)
function deepcopy_internal(@nospecialize(x), stackdict::IdDict)
T = typeof(x)::DataType
nf = nfields(x)
(isbits(T) || nf == 0) && return x
Expand All @@ -71,14 +71,14 @@ function deepcopy_internal(@nospecialize(x), stackdict::ObjectIdDict)
return y::T
end

function deepcopy_internal(x::Array, stackdict::ObjectIdDict)
function deepcopy_internal(x::Array, stackdict::IdDict)
if haskey(stackdict, x)
return stackdict[x]
end
_deepcopy_array_t(x, eltype(x), stackdict)
end

function _deepcopy_array_t(@nospecialize(x), T, stackdict::ObjectIdDict)
function _deepcopy_array_t(@nospecialize(x), T, stackdict::IdDict)
if isbits(T)
return (stackdict[x]=copy(x))
end
Expand All @@ -96,7 +96,7 @@ function _deepcopy_array_t(@nospecialize(x), T, stackdict::ObjectIdDict)
return dest
end

function deepcopy_internal(x::Dict, stackdict::ObjectIdDict)
function deepcopy_internal(x::Dict, stackdict::IdDict)
if haskey(stackdict, x)
return stackdict[x]::typeof(x)
end
Expand All @@ -112,4 +112,3 @@ function deepcopy_internal(x::Dict, stackdict::ObjectIdDict)
end
dest
end

3 changes: 3 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3423,6 +3423,9 @@ workspace() = error("workspace() is discontinued, check out Revise.jl for an alt
# PR #25113
@deprecate_binding CartesianRange CartesianIndices

# ObjectIdDict -> IdDict
@deprecate_binding ObjectIdDict IdDict{Any,Any} # true " use IdDict{Any,Any} instead" # TODO

# END 0.7 deprecations

# BEGIN 1.0 deprecations
Expand Down
100 changes: 100 additions & 0 deletions base/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -800,3 +800,103 @@ empty(::ImmutableDict, ::Type{K}, ::Type{V}) where {K, V} = ImmutableDict{K,V}()

_similar_for(c::Dict, ::Type{Pair{K,V}}, itr, isz) where {K, V} = empty(c, K, V)
_similar_for(c::AbstractDict, T, itr, isz) = throw(ArgumentError("for AbstractDicts, similar requires an element type of Pair;\n if calling map, consider a comprehension instead"))

#####
# Hashing with Object-ID

# Hashing by identify with parameters

"""
IdDict([itr])

`IdDict{K,V}()` constructs a hash table using object-id as hash and
`===` as equality with keys of type `K` and values of type `V`.

See [`Dict`](@ref) for further help.
"""
struct IdDict{K,V} <: AbstractDict{K,V}
ht::_ObjectIdDict

IdDict{K,V}() where {K, V} = new(_ObjectIdDict())
IdDict{K,V}(d::IdDict{K,V}) where {K, V} = new(copy(d.ht))
end

function IdDict{K,V}(kv) where {K, V}
d = IdDict{K,V}()
for (k,v) in kv
d[k] = v
end
return d
end
IdDict{K,V}(p::Pair) where {K,V} = setindex!(IdDict{K,V}(), p.second, p.first)
function IdDict{K,V}(ps::Pair...) where {K, V}
d = IdDict{K,V}()
sizehint!(d, length(ps))
for p in ps
d[p.first] = p.second
end
return d
end
IdDict() = IdDict{Any,Any}()
IdDict(kv::Tuple{}) = IdDict()
copy(d::IdDict) = IdDict(d)

IdDict(ps::Pair{K,V}...) where {K,V} = IdDict{K,V}(ps)
IdDict(ps::Pair{K}...) where {K} = IdDict{K,Any}(ps)
IdDict(ps::(Pair{K,V} where K)...) where {V} = IdDict{Any,V}(ps)
IdDict(ps::Pair...) = IdDict{Any,Any}(ps)

function IdDict(kv)
try
dict_with_eltype((K, V) -> IdDict{K, V}, kv, eltype(kv))
catch e
if !applicable(start, kv) || !all(x->isa(x,Union{Tuple,Pair}),kv)
throw(ArgumentError(
"IdDict(kv): kv needs to be an iterator of tuples or pairs"))
else
rethrow(e)
end
end
end

empty(d::IdDict, ::Type{K}, ::Type{V}) where {K, V} = IdDict{K,V}()
empty!(d::IdDict) = (empty!(d.ht); d)

rehash!(d::IdDict) = (rehash!(d.ht); d)

sizehint!(d::IdDict, newsz) = (sizehint!(d.ht, newsz); d)

function getindex(d::IdDict{K,V}, key::K) where {K, V}
v = get(d.ht, key, secret_table_token)
v == secret_table_token ? throw(KeyError(key)) : v::V
end
setindex!(d::IdDict{K,V}, v, k::K) where {K, V} = setindex!(d.ht, convert(V, v), k)

function get(d::IdDict{K,V}, key::K, default) where {K, V}
v = get(d.ht, key, secret_table_token)
v == secret_table_token ? default : v::V
end
get!(d::IdDict{K,V}, key::K, default::V) where {K, V} =
(d[key] = get(d.ht, key, default))::V

function pop!(d::IdDict{K,V}, key::K, default) where {K, V}
v = pop!(d.ht, key, secret_table_token)
v == secret_table_token ? default : v::V
end
pop!(d::IdDict{K,V}, key::K) where {K, V} = pop!(d.ht, key)::V

function delete!(d::IdDict{K}, key::K) where {K}
delete!(d.ht, key)
d
end

start(d::IdDict) = start(d.ht)
done(d::IdDict, i) = done(d.ht, i)
next(d::IdDict{K,V}, i) where {K, V} =
(Pair{K,V}(d.ht.ht[i+1], d.ht.ht[i+2]), _oidd_nextind(d.ht.ht, i+2))

length(d::IdDict) = length(d.ht)

# For some AbstractDict types, it is safe to implement filter!
# by deleting keys during iteration.
filter!(f, d::IdDict) = filter_in_one_pass!(f, d)
10 changes: 5 additions & 5 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ export doc
const modules = Module[]
const META = gensym(:meta)

meta(m::Module) = isdefined(m, META) ? getfield(m, META) : ObjectIdDict()
meta(m::Module) = isdefined(m, META) ? getfield(m, META) : IdDict()

function initmeta(m::Module)
if !isdefined(m, META)
eval(m, :(const $META = $(ObjectIdDict())))
eval(m, :(const $META = $(IdDict())))
push!(modules, m)
end
nothing
Expand Down Expand Up @@ -216,9 +216,9 @@ mutable struct MultiDoc
"Ordered (via definition order) vector of object signatures."
order::Vector{Type}
"Documentation for each object. Keys are signatures."
docs::ObjectIdDict
docs::IdDict

MultiDoc() = new(Type[], ObjectIdDict())
MultiDoc() = new(Type[], IdDict())
end

# Docstring registration.
Expand Down Expand Up @@ -712,7 +712,7 @@ function docm(source::LineNumberNode, mod::Module, meta, ex, define = true)

# All other expressions are undocumentable and should be handled on a case-by-case basis
# with `@__doc__`. Unbound string literals are also undocumentable since they cannot be
# retrieved from the module's metadata `ObjectIdDict` without a reference to the string.
# retrieved from the module's metadata `IdDict` without a reference to the string.
docerror(ex)
end

Expand Down
2 changes: 1 addition & 1 deletion base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export
MergeSort,
Missing,
NTuple,
ObjectIdDict,
IdDict,
OrdinalRange,
Pair,
PartialQuickSort,
Expand Down
Loading