Skip to content

Commit

Permalink
allow specializing Base.hash for enum types without overwriting method
Browse files Browse the repository at this point in the history
Previously `@enum` defined `Base.hash(::MyEnumType, ::UInt)` on the
user-defined enum type `MyEnumType`.
When the user wants to specialize the hash function for his own enum
type he will define exactly that method signature again which overwrites
it and leads to the warning

    WARNING: Method definition hash(TestPackage.MyEnumType, UInt64) in module TestPackage at Enums.jl:210 overwritten at [...]
      ** incremental compilation may be fatally broken for this module **

This commit changes `@enum` so that an internal method is used instead
which is called through a fallback `Base.hash(::Enum, ::UInt)`.
  • Loading branch information
stev47 committed May 23, 2023
1 parent 944b28c commit 72f6f64
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 2 deletions.
16 changes: 14 additions & 2 deletions base/Enums.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ Base.cconvert(::Type{T}, x::Enum{T2}) where {T<:Integer,T2<:Integer} = T(x)::T
Base.write(io::IO, x::Enum{T}) where {T<:Integer} = write(io, T(x))
Base.read(io::IO, ::Type{T}) where {T<:Enum} = T(read(io, basetype(T)))

"""
_enum_hash(x::Enum, h::UInt)
Compute hash for an enum value `x`. This internal method will be specialized
for every enum type created through [`@enum`](@ref).
"""
_enum_hash(x::Enum, h::UInt) = hash(x, h)
Base.hash(x::Enum, h::UInt) = _enum_hash(x, h)
Base.isless(x::T, y::T) where {T<:Enum} = isless(basetype(T)(x), basetype(T)(y))

Base.Symbol(x::Enum) = namemap(typeof(x))[Integer(x)]::Symbol
Expand Down Expand Up @@ -206,8 +214,12 @@ macro enum(T::Union{Symbol,Expr}, syms...)
Enums.namemap(::Type{$(esc(typename))}) = $(esc(namemap))
Base.typemin(x::Type{$(esc(typename))}) = $(esc(typename))($lo)
Base.typemax(x::Type{$(esc(typename))}) = $(esc(typename))($hi)
let enum_hash = hash($(esc(typename)))
Base.hash(x::$(esc(typename)), h::UInt) = hash(enum_hash, hash(Integer(x), h))
let type_hash = hash($(esc(typename)))
# Use internal `_enum_hash` to allow users to specialize
# `Base.hash` for their own enum types without overwriting the
# method we would define here. This avoids a warning for
# precompilation.
Enums._enum_hash(x::$(esc(typename)), h::UInt) = hash(type_hash, hash(Integer(x), h))
end
let insts = (Any[ $(esc(typename))(v) for v in $values ]...,)
Base.instances(::Type{$(esc(typename))}) = insts
Expand Down
5 changes: 5 additions & 0 deletions test/enums.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ end
@enum HashEnum2 Enum2_a=1
@test hash(Enum1_a) != hash(Enum2_a)

# PR #49777: Check that `Base.hash` can be specialized by the user without
# overwriting a method definition.
@enum HashEnum3 Enum3_a=1
@test which(hash, (HashEnum3, UInt)).sig != Tuple{typeof(hash), HashEnum3, UInt64}

@test (Vector{Fruit}(undef, 3) .= apple) == [apple, apple, apple]

# long, discongruous
Expand Down

0 comments on commit 72f6f64

Please sign in to comment.