diff --git a/LICENSE.md b/LICENSE.md index 5d34e61..616acd4 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,7 +2,7 @@ The AutoHashEquals.jl package is licensed under the MIT "Expat" License: -Copyright (c) 2015-2023: andrew cooke, RelationalAI, Inc, and contributors. +Copyright (c) 2015-2023: Neal Gafter, Andrew Cooke, RelationalAI, Inc, and contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Project.toml b/Project.toml index b7d85bf..c0cd36f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "AutoHashEquals" uuid = "15f4f7f2-30c1-5605-9d31-71845cf9641f" authors = ["Neal Gafter ", "andrew cooke "] -version = "2.0.0" +version = "2.1.0" [deps] Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/README.md b/README.md index 5ef7d1b..244cd3a 100644 --- a/README.md +++ b/README.md @@ -214,3 +214,10 @@ end @assert Box891(missing) != Box891(1) @assert !isequal(Box891(missing), Box891(1)) ``` + +If you need compatibility mode always and don't want to have to specify the mode on each invocation, +you can instead import the compatibility version of the macro, which defaults to `compat1=true': + +```julia +using AutoHashEquals.Compat +``` diff --git a/src/AutoHashEquals.jl b/src/AutoHashEquals.jl index 362c454..c3255b9 100644 --- a/src/AutoHashEquals.jl +++ b/src/AutoHashEquals.jl @@ -6,6 +6,7 @@ export @auto_hash_equals include("type_seed.jl") include("impl.jl") +include("compat.jl") """ @auto_hash_equals [options] struct Foo ... end diff --git a/src/compat.jl b/src/compat.jl new file mode 100644 index 0000000..d1c3983 --- /dev/null +++ b/src/compat.jl @@ -0,0 +1,25 @@ +module Compat + +using AutoHashEquals: AutoHashEquals + +export @auto_hash_equals + +""" + @auto_hash_equals [options] struct Foo ... end + +Generate `Base.hash`, `Base.isequal`, and `Base.==` methods for `Foo`. + +Options: + +* `cache=true|false` whether or not to generate an extra cache field to store the precomputed hash value. Default: `false`. +* `hashfn=myhash` the hash function to use. Default: `Base.hash`. +* `fields=a,b,c` the fields to use for hashing and equality. Default: all fields. +* `typearg=true|false` whether or not to make type arguments significant. Default: `false`. +* `typeseed=e` Use `e` (or `e(type)` if `typearg=true`) as the seed for hashing type arguments. +* `compat1=true` To have `==` defined by using `isequal`. Default: `true`. +""" +macro auto_hash_equals(args...) + esc(:($AutoHashEquals.@auto_hash_equals(compat1=true, $(args...)))) +end + +end diff --git a/src/impl.jl b/src/impl.jl index 52efdbd..f627cf8 100644 --- a/src/impl.jl +++ b/src/impl.jl @@ -234,7 +234,7 @@ function auto_hash_equals_impl(__source__, struct_decl, fields, cache::Bool, has if typearg :($type_seed($full_type_name)) else - Base.hash(type_name) + :($hashfn($(QuoteNode(type_name)))) end else if typearg @@ -275,11 +275,11 @@ function auto_hash_equals_impl(__source__, struct_decl, fields, cache::Bool, has if typearg :($type_seed($full_type_name, h)) else - :($(Base.hash)($(QuoteNode(type_name)), h)) + :($hashfn($(QuoteNode(type_name)), h)) end else if typearg - :(h + UInt($typeseed($full_type_name))) + :(UInt($typeseed($full_type_name, h))) else :(h + UInt($typeseed)) end @@ -360,9 +360,6 @@ function auto_hash_equals_impl(__source__, struct_decl, fields, cache::Bool, has equality_impl = :(a === b || $equality_impl) end else - # Julia library defines `isequal` in terms of `==`. - compat1 && continue - # Here we have a more complicated implementation in order to handle missings correctly. # If any field comparison is false, we return false (even if some return missing). # If no field comparisons are false, but one comparison missing, then we return missing. diff --git a/test/compat.jl b/test/compat.jl new file mode 100644 index 0000000..6d7b4d6 --- /dev/null +++ b/test/compat.jl @@ -0,0 +1,16 @@ +module compat + +using AutoHashEquals.Compat: @auto_hash_equals +using Test + +@testset "test the compat macro" begin + @auto_hash_equals struct Box851{T} + x::T + end + @test Box851(missing) == Box851(missing) + @test isequal(Box851(missing), Box851(missing)) + @test Box851(missing) != Box851(1) + @test !isequal(Box851(missing), Box851(1)) +end + +end diff --git a/test/runtests.jl b/test/runtests.jl index 14df048..98d0af4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,8 +1,10 @@ # SPDX-License-Identifier: MIT +include("compat.jl") + module runtests -using AutoHashEquals: @auto_hash_equals +using AutoHashEquals: AutoHashEquals, @auto_hash_equals using Markdown: plain using Match: Match, @match, MatchFailure using Random @@ -23,15 +25,6 @@ macro noop(x) esc(x) end -macro _const(x) - # const fields were introduced in Julia 1.8 - if VERSION >= v"1.8" - esc(Expr(:const , x)) - else - esc(x) - end -end - """ @auto_hash_equals_cached struct Foo ... end @@ -114,14 +107,12 @@ abstract type B{T} end end @testset "the macro sees through `const`" begin - if VERSION >= v"1.8" - T33 = eval(:(@auto_hash_equals mutable struct T33 - @_const x - end)) - @test T33(1) == T33(1) - @test hash(T33(1)) == hash(T33(1)) - @test hash(T33(1)) != hash(T33(2)) - end + T33 = eval(:(@auto_hash_equals mutable struct T33 + const x + end)) + @test T33(1) == T33(1) + @test hash(T33(1)) == hash(T33(1)) + @test hash(T33(1)) != hash(T33(2)) end @testset "misuse of the macro" begin @@ -288,12 +279,7 @@ abstract type B{T} end end # @test_throws requires a type before v1.8. - internal_constructor_error = - if VERSION >= v"1.7" - ErrorException - else - LoadError - end + internal_constructor_error = ErrorException @testset "give an error if the struct contains internal constructors 1" begin @test_throws internal_constructor_error begin @@ -663,89 +649,49 @@ abstract type B{T} end x end - if VERSION < v"1.7" - @test 0x67d66c8ebce604c4 === hash(Box1(1)) - @test 0x57ce10fa6d65774c === hash(Box1(:x)) - @test 0x7951851906420162 === hash(Box1("a")) - @test 0x6a46c6ef41c6b97d === hash(Box1(1), UInt(1)) - @test 0x0ef668a2dd4500a0 === hash(Box1(:x), UInt(1)) - @test 0x7398684da66deba5 === hash(Box1("a"), UInt(1)) - else - @test 0x05014b35fc91d289 === hash(Box1(1)) - @test 0x91d7652c7a24efb3 === hash(Box1(:x)) - @test 0x1d9ac96f957cc50a === hash(Box1("a")) - @test 0x6e0378444e962be8 === hash(Box1(1), UInt(1)) - @test 0xa31a1cd3c72d944c === hash(Box1(:x), UInt(1)) - @test 0xe563b59c847e3d2f === hash(Box1("a"), UInt(1)) - end + @test 0x05014b35fc91d289 === hash(Box1(1)) + @test 0x91d7652c7a24efb3 === hash(Box1(:x)) + @test 0x1d9ac96f957cc50a === hash(Box1("a")) + @test 0x6e0378444e962be8 === hash(Box1(1), UInt(1)) + @test 0xa31a1cd3c72d944c === hash(Box1(:x), UInt(1)) + @test 0xe563b59c847e3d2f === hash(Box1("a"), UInt(1)) @auto_hash_equals struct Box2{T} x::T end - if VERSION < v"1.7" - @test 0x97e8e85cce6400e5 === hash(Box2(1)) - @test 0x97e8e85cce6400e5 === hash(Box2{Any}(1)) - @test 0x95c1c5ce8a9d4310 === hash(Box2(:x)) - @test 0x9424a3ad9ea0312c === hash(Box2("a")) - @test 0xd7caed9a4e280b13 === hash(Box2(1), UInt(1)) - @test 0xd7caed9a4e280b13 === hash(Box2{Any}(1), UInt(1)) - @test 0x3c6236446852acfb === hash(Box2(:x), UInt(1)) - @test 0x08aaed0ddd68f482 === hash(Box2("a"), UInt(1)) - else - @test 0xfddfe30b106aa2f0 === hash(Box2(1)) - @test 0xfddfe30b106aa2f0 === hash(Box2{Any}(1)) - @test 0xb9abdfa5883b32bb === hash(Box2(:x)) - @test 0x6c49b14653a071c6 === hash(Box2("a")) - @test 0x451b0ebf9ee0f99c === hash(Box2(1), UInt(1)) - @test 0x451b0ebf9ee0f99c === hash(Box2{Any}(1), UInt(1)) - @test 0x175e9079609f34c5 === hash(Box2(:x), UInt(1)) - @test 0x77cf64ab93060d1e === hash(Box2("a"), UInt(1)) - end + @test 0xfddfe30b106aa2f0 === hash(Box2(1)) + @test 0xfddfe30b106aa2f0 === hash(Box2{Any}(1)) + @test 0xb9abdfa5883b32bb === hash(Box2(:x)) + @test 0x6c49b14653a071c6 === hash(Box2("a")) + @test 0x451b0ebf9ee0f99c === hash(Box2(1), UInt(1)) + @test 0x451b0ebf9ee0f99c === hash(Box2{Any}(1), UInt(1)) + @test 0x175e9079609f34c5 === hash(Box2(:x), UInt(1)) + @test 0x77cf64ab93060d1e === hash(Box2("a"), UInt(1)) @auto_hash_equals struct Box3 x end - if VERSION < v"1.7" - @test 0xa28c5530534e00ff === hash(Box3(1)) - @test 0xbd098dc8d84b2b3c === hash(Box3(:x)) - @test 0x306232d62b351152 === hash(Box3("a")) - @test 0xd4f16da2b818329f === hash(Box3(1), UInt(1)) - @test 0xbc02b85a84d59f22 === hash(Box3(:x), UInt(1)) - @test 0xf3298984f3d3f10e === hash(Box3("a"), UInt(1)) - else - @test 0x6c8a62ecebe7d0ce === hash(Box3(1)) - @test 0xb3dc0f774c8dbf65 === hash(Box3(:x)) - @test 0x18c77bdc2543b944 === hash(Box3("a")) - @test 0x1fe5e7cdd29edab1 === hash(Box3(1), UInt(1)) - @test 0x55e8647bf53d5ecd === hash(Box3(:x), UInt(1)) - @test 0xf556f204c1f1bc53 === hash(Box3("a"), UInt(1)) - end + @test 0x6c8a62ecebe7d0ce === hash(Box3(1)) + @test 0xb3dc0f774c8dbf65 === hash(Box3(:x)) + @test 0x18c77bdc2543b944 === hash(Box3("a")) + @test 0x1fe5e7cdd29edab1 === hash(Box3(1), UInt(1)) + @test 0x55e8647bf53d5ecd === hash(Box3(:x), UInt(1)) + @test 0xf556f204c1f1bc53 === hash(Box3("a"), UInt(1)) @auto_hash_equals struct Box4{T} x::T end - if VERSION < v"1.7" - @test 0xa0164c66e926af40 === hash(Box4(1)) - @test 0xa0164c66e926af40 === hash(Box4{Any}(1)) - @test 0xcb0ce1b2da05840b === hash(Box4(:x)) - @test 0xc10479084e27e5db === hash(Box4("a")) - @test 0xdbc4ab0260836c4a === hash(Box4(1), UInt(1)) - @test 0xdbc4ab0260836c4a === hash(Box4{Any}(1), UInt(1)) - @test 0x485f0ce7fd57b390 === hash(Box4(:x), UInt(1)) - @test 0xaff3b9595e40223d === hash(Box4("a"), UInt(1)) - else - @test 0x98dc0cd9a86cbdee === hash(Box4(1)) - @test 0x98dc0cd9a86cbdee === hash(Box4{Any}(1)) - @test 0x3dbd99c859966133 === hash(Box4(:x)) - @test 0xa7d6e8579ef5a8cd === hash(Box4("a")) - @test 0x44ac08ef000cb686 === hash(Box4(1), UInt(1)) - @test 0x44ac08ef000cb686 === hash(Box4{Any}(1), UInt(1)) - @test 0xc7dc8347992b452d === hash(Box4(:x), UInt(1)) - @test 0x3dcb6b6168a2c18d === hash(Box4("a"), UInt(1)) - end + @test 0x98dc0cd9a86cbdee === hash(Box4(1)) + @test 0x98dc0cd9a86cbdee === hash(Box4{Any}(1)) + @test 0x3dbd99c859966133 === hash(Box4(:x)) + @test 0xa7d6e8579ef5a8cd === hash(Box4("a")) + @test 0x44ac08ef000cb686 === hash(Box4(1), UInt(1)) + @test 0x44ac08ef000cb686 === hash(Box4{Any}(1), UInt(1)) + @test 0xc7dc8347992b452d === hash(Box4(:x), UInt(1)) + @test 0x3dcb6b6168a2c18d === hash(Box4("a"), UInt(1)) end @@ -806,7 +752,7 @@ abstract type B{T} end w::T end @test hash(S640{Int}(1)) == hash(1, hash(S640{Int})) - @test hash(S640{Int}(1), UInt(2)) == hash(1, UInt(2) + hash(S640{Int})) + @test hash(S640{Int}(1), UInt(2)) == hash(1, hash(S640{Int}, UInt(2))) end @testset "test typearg=false typeseed=K generic type" begin @@ -822,7 +768,7 @@ abstract type B{T} end w end @test hash(S650(1)) == hash(1, hash(S650)) - @test hash(S650(1), UInt(2)) == hash(1, UInt(2) + hash(S650)) + @test hash(S650(1), UInt(2)) == hash(1, hash(S650, UInt(2))) end @testset "test typearg=false typeseed=e non-generic type" begin @@ -902,6 +848,7 @@ abstract type B{T} end @test Box891(missing) != Box891(1) @test !isequal(Box891(missing), Box891(1)) end + end end