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

Add typeseed=e parameter to @auto_hash_equals. #44

Merged
merged 12 commits into from
Aug 29, 2023
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ julia> Box2{Int}(1) == Box2{Any}(1)
false

julia> hash(Box2{Int}(1))
0xb7650cb555d6aafa
0x467811eefea1d458

julia> hash(Box2{Any}(1))
0xefe691a94f296c61
0x3042fd2f8fe839d7
```

## Specifying the "type seed"
Expand Down
1 change: 1 addition & 0 deletions src/AutoHashEquals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module AutoHashEquals

export @auto_hash_equals

include("type_key.jl")
include("impl.jl")

"""
Expand Down
8 changes: 4 additions & 4 deletions src/impl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,9 @@ function auto_hash_equals_impl(__source__, struct_decl, fields, cache::Bool, has
# Add the internal constructor
hash_init = if isnothing(typeseed)
if typearg
:($hashfn($full_type_name))
:($type_key($full_type_name))
else
:($hashfn($(QuoteNode(type_name))))
Base.hash(type_name)
end
else
if typearg
Expand Down Expand Up @@ -267,9 +267,9 @@ function auto_hash_equals_impl(__source__, struct_decl, fields, cache::Bool, has
hash_init =
if isnothing(typeseed)
if typearg
:($hashfn($full_type_name, h))
:($type_key($full_type_name, h))
else
:($hashfn($(QuoteNode(type_name)), h))
:($(Base.hash)($(QuoteNode(type_name)), h))
end
else
if typearg
Expand Down
76 changes: 76 additions & 0 deletions src/type_key.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
type_key(x)

Computes a value to use as a seed for computing the hash value of a type.
"""
function type_key end

function type_key(m::Module, h::UInt)
if m === Main || m === Base || m === Core
return h
else
mp = parentmodule(m)
if mp === m
return h
else
return hash(nameof(m), type_key(mp, h))
end
end
end

function type_key(t::UnionAll, h::UInt)
h = hash(h, 0xbd5f3e4941dba79d)
gafter marked this conversation as resolved.
Show resolved Hide resolved
h = type_key(t.var, h)
h = type_key(t.body, h)
h
end

function type_key(x::Union, h::UInt)
h = hash(h, 0xa16a31201c4852c2)
for t in Base.uniontypes(x)
h = type_key(t, h)
end
return h
end

function type_key(t::TypeVar, h::UInt)
h = hash(h, 0xc921c42a65aee273)
h = hash(t.name, h)
h = type_key(t.lb, h)
h = type_key(t.ub, h)
return h
end

function type_key(t::DataType, h::UInt)
h = hash(h, 0x5e90a8c7e280e39b)
tn = t.name
if (isdefined(tn, :module))
h0 = 0xae72cbeead1b2d46
h0 = type_key(tn.module, h0)
h = hash(h, h0)
end
h = hash(tn.name, h)
if !isempty(t.parameters)
h0 = 0x9f86d3fbe4382c06
for p in t.parameters
h0 = type_key(p, h0)
end
h = hash(h, h0)
end
return h
end

function type_key(t::(typeof(Union{})), h::UInt)
return hash(h, 0x68f57dd85252e163)
end

function type_key(t::Core.TypeofVararg, h::UInt)
h = hash(h, 0xe7f2ebfee436674d)
isdefined(t, :T) && (h = type_key(t.T, h))
isdefined(t, :N) && (h = type_key(t.N, h))
return h
end

type_key(x, h::UInt) = Base.hash(x, h)

type_key(x) = type_key(x, UInt(0))
65 changes: 58 additions & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ using Random
using Serialization
using Test

# Import private member for test purposes
using AutoHashEquals: type_key

function serialize_and_deserialize(x)
buf = IOBuffer()
serialize(buf, x)
Expand Down Expand Up @@ -64,6 +67,15 @@ end
ignore_me
end

struct G{T, U}
x::T
y::U
end

abstract type Q end
abstract type B{T} end
@enum E e1 e2 e3

@testset "AutoHashEquals.jl" begin

@testset "tests for @auto_hash_equals_cached" begin
Expand Down Expand Up @@ -160,7 +172,7 @@ end
y::G
end
@test T63{Symbol}(1, :x) isa T63
@test hash(T63{Symbol}(1, :x)) == hash(:x,hash(1,hash(T63{Symbol})))
@test hash(T63{Symbol}(1, :x)) == hash(:x,hash(1,type_key(T63{Symbol})))
@test hash(T63{Symbol}(1, :x)) != hash(T63{Any}(1, :x))
@test T63{Symbol}(1, :x) != T63{Any}(1, :x) # note: type args are significant
@test T63{Symbol}(1, :x) == T63{Symbol}(1, :x)
Expand Down Expand Up @@ -195,8 +207,8 @@ end
@test T107a(1) == T107a(1)
@test T107a(1) == serialize_and_deserialize(T107a(1))
@test T107a(1) != T107a(2)
@test hash(T107a(1)) == hash(1, hash(T107a{Int}))
@test hash(T107a("x")) == hash("x", hash(T107a{String}))
@test hash(T107a(1)) == hash(1, type_key(T107a{Int}))
@test hash(T107a("x")) == hash("x", type_key(T107a{String}))
@test hash(T107a(1)) != hash(T107b(1))
@test hash(T107a(1)) != hash(T107a(2))
end
Expand Down Expand Up @@ -591,8 +603,8 @@ end
@auto_hash_equals typearg=true struct S590{T}
x::T
end
@test hash(S590{Int}(1)) == hash(1, hash(S590{Int}, UInt(0)))
@test hash(S590{Int}(1), UInt(0x2)) == hash(1, hash(S590{Int}, UInt(0x2)))
@test hash(S590{Int}(1)) == hash(1, type_key(S590{Int}, UInt(0)))
@test hash(S590{Int}(1), UInt(0x2)) == hash(1, type_key(S590{Int}, UInt(0x2)))
@test S590{Int}(1) != S590{Any}(1)
@test hash(S590{Int}(1)) != hash(S590{Any}(1))
end
Expand All @@ -601,8 +613,8 @@ end
@auto_hash_equals typearg=true cache=true struct S597{T}
x::T
end
@test hash(S597{Int}(1)) == hash(1, hash(S597{Int}, UInt(0)))
@test hash(S597{Int}(1), UInt(0x2)) == hash(hash(1, hash(S597{Int}, UInt(0))), UInt(0x2))
@test hash(S597{Int}(1)) == hash(1, type_key(S597{Int}, UInt(0)))
@test hash(S597{Int}(1), UInt(0x2)) == hash(hash(1, type_key(S597{Int}, UInt(0))), UInt(0x2))
end

@testset "Test when type NOT included in hash 1" begin
Expand Down Expand Up @@ -634,6 +646,45 @@ end
@test Box629{Int}(1) == Box629{Any}(1)
@test hash(Box629{Int}(1)) == hash(Box629{Any}(1))
end

@testset "ensure that type_key(x) is stable" begin
@test 0x4965055ca9c88623 === type_key(Int)
@test 0x0df062180f6b5880 === type_key(String)

@test 0xf9a6ca5801d45b09 === type_key(G)
@test 0xf9a6ca5801d45b09 === type_key(G{T, U} where { T, U })
@test 0xf53284f9e807b4a3 === type_key(G{T, U} where { T <: Int, U <: String })
@test 0x5cb3bb63536c6cf1 === type_key(G{Int, String})
@test 0xa7672606502d460e === type_key(G{Int, T} where T)
@test 0x355f38500af67860 === type_key(G{T, String} where T)
@test 0x9f30a357e851217f === type_key(G{T, T} where T)
@test 0x9f30a357e851217f === type_key(G{T, T} where T)
@test 0x3d744a467126be1e === type_key(G{G{T, Int}, G{T, String}} where T)
@test 0x3d744a467126be1e === type_key(G{G{T, Int}, G{T, String}} where T)

@test 0x538beca01f293a83 === type_key(Q)
@test 0x77cb5d6464d056b2 === type_key(B)
@test 0x77cb5d6464d056b2 === type_key(B{T} where { T })
@test 0x16d5d946443d9674 === type_key(B{T} where { T <: Int })
@test 0x99cfdc2e3e9a8840 === type_key(B{Int64})

@test 0xba0f522abd7271e6 === type_key(Any)
@test 0x9514191d828c99aa === type_key(Union{Int, String})
@test 0x3cef2865f9232667 === type_key(Union{})
@test 0xb526450531768469 === type_key(Union)

@test 0x024013567c619983 === type_key(E)
@test 0x0bebb7628e67fe95 === type_key(Tuple)
@test 0xaed8787afb424a79 === type_key(Tuple{})
@test 0xaed8787afb424a79 === type_key(NTuple{0, Int})
@test 0xfb53d28e571bdb76 === type_key(Tuple{String})
@test 0xfb53d28e571bdb76 === type_key(NTuple{1, String})
@test 0x9a568ffe5fe206a4 === type_key(Tuple{String, Int})
@test 0xefd4727ccbbc5397 === type_key(typeof(@NamedTuple{a::Int, b::String}))
@test 0x7e20703885eb3e39 === type_key(NTuple)
@test 0x157c81572eba93c3 === type_key(NTuple{3, Int})
@test hash(1) === type_key(1)
end
end

@testset "test option typeseed=e" begin
Expand Down