Skip to content

Commit

Permalink
Define a method for hash(::Type, ::UInt) (#49636)
Browse files Browse the repository at this point in the history
Currently, `hash(::Type, ::UInt)` uses `objectid`, which can have some
odd behavior for types: in particular, subsequent identical type-valued
variable definitions can have `objectid`s which differ from the first
such definition. This has some bizarre downstream effects when e.g.
using types as the values of a `Set` or the keys of a `Dict`. See issue
49620 for examples.

There is an internal `type_hash` C function used for caching types but
isn't exposed to Julia, as Jameson pointed out in the linked issue. This
commit exposes it as `jl_type_hash` which is then used via `ccall` to
define a method `hash(::Type, ::UInt)`. This method then fixes #49620.
Note, however, that this does not affect the differing `objectid`s for
otherwise identical types.
  • Loading branch information
ararslan committed May 6, 2023
1 parent 9bb0731 commit 633d1ae
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 0 deletions.
1 change: 1 addition & 0 deletions base/hashing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ See also: [`objectid`](@ref), [`Dict`](@ref), [`Set`](@ref).
"""
hash(x::Any) = hash(x, zero(UInt))
hash(w::WeakRef, h::UInt) = hash(w.value, h)
hash(T::Type, h::UInt) = hash_uint(3h - ccall(:jl_type_hash, UInt, (Any,), T))

## hashing general objects ##

Expand Down
8 changes: 8 additions & 0 deletions src/jltypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,14 @@ static unsigned type_hash(jl_value_t *kj, int *failed) JL_NOTSAFEPOINT
}
}

JL_DLLEXPORT uintptr_t jl_type_hash(jl_value_t *v) JL_NOTSAFEPOINT
{
// NOTE: The value of `failed` is purposefully ignored here. The parameter is relevant
// for other parts of the internal algorithm but not for exposing to the Julia side.
int failed = 0;
return type_hash(v, &failed);
}

static unsigned typekey_hash(jl_typename_t *tn, jl_value_t **key, size_t n, int nofail) JL_NOTSAFEPOINT
{
if (tn == jl_type_typename && key[0] == jl_bottom_type)
Expand Down
1 change: 1 addition & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,7 @@ JL_DLLEXPORT int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_v
JL_DLLEXPORT int jl_egal__special(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT;
JL_DLLEXPORT int jl_egal__unboxed(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT;
JL_DLLEXPORT uintptr_t jl_object_id(jl_value_t *v) JL_NOTSAFEPOINT;
JL_DLLEXPORT uintptr_t jl_type_hash(jl_value_t *v) JL_NOTSAFEPOINT;

STATIC_INLINE int jl_egal__unboxed_(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT
{
Expand Down
7 changes: 7 additions & 0 deletions test/hashing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,10 @@ if Sys.WORD_SIZE >= 64
objectid(s)
end
end

# Issue #49620
let t1 = Tuple{AbstractVector,AbstractVector{<:Integer},UnitRange{<:Integer}},
t2 = Tuple{AbstractVector,AbstractVector{<:Integer},UnitRange{<:Integer}}
@test hash(t1) == hash(t2)
@test length(Set{Type}([t1, t2])) == 1
end

0 comments on commit 633d1ae

Please sign in to comment.