From c3e6dc29365f2de57441a4bd9620c17474f67588 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Thu, 7 May 2026 21:09:30 -0400 Subject: [PATCH 1/3] make hash.fromUniversalHashCode null safe and add tests --- kernel/src/main/scala/cats/kernel/Hash.scala | 2 +- .../src/test/scala/cats/tests/HashSuite.scala | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/kernel/src/main/scala/cats/kernel/Hash.scala b/kernel/src/main/scala/cats/kernel/Hash.scala index 02bb7252ba..7b5b8f60c1 100644 --- a/kernel/src/main/scala/cats/kernel/Hash.scala +++ b/kernel/src/main/scala/cats/kernel/Hash.scala @@ -71,7 +71,7 @@ object Hash extends HashFunctions[Hash] { */ def fromUniversalHashCode[A]: Hash[A] = new Hash[A] { - def hash(x: A) = x.hashCode() + def hash(x: A) = x.## def eqv(x: A, y: A) = x == y } } diff --git a/tests/shared/src/test/scala/cats/tests/HashSuite.scala b/tests/shared/src/test/scala/cats/tests/HashSuite.scala index d70f562a45..598f4e24c4 100644 --- a/tests/shared/src/test/scala/cats/tests/HashSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/HashSuite.scala @@ -35,7 +35,24 @@ class HashSuite extends CatsSuite { Contravariant[Hash] } - assert(1.hash == 1.hashCode) - assert("ABC".hash == "ABC".hashCode) + test("hash extension method is consistent with hashCode for Int") { + assert(1.hash == 1.hashCode) + } + + test("hash extension method is consistent with hashCode for String") { + assert("ABC".hash == "ABC".hashCode) + } + + test("fromUniversalHashCode should be consistent with hashCode on non-null references") { + case class ExplicitHashCode() { override val hashCode = 42 } + val hash = Hash.fromUniversalHashCode[ExplicitHashCode] + assertEquals(hash.hash(ExplicitHashCode()), 42) + } + + test("fromUniversalHashCode should be null safe") { + val hash = Hash.fromUniversalHashCode[AnyRef] + assertEquals(hash.hash(null), 0) + } + checkAll("Defer[Hash]", DeferTests[Hash].defer[MiniInt]) } From 19b69db7585ac339f85466160256060273862743 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Sun, 10 May 2026 18:14:11 -0400 Subject: [PATCH 2/3] make ExplicitHashCode a regular class --- tests/shared/src/test/scala/cats/tests/HashSuite.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/shared/src/test/scala/cats/tests/HashSuite.scala b/tests/shared/src/test/scala/cats/tests/HashSuite.scala index 598f4e24c4..d578844041 100644 --- a/tests/shared/src/test/scala/cats/tests/HashSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/HashSuite.scala @@ -44,9 +44,9 @@ class HashSuite extends CatsSuite { } test("fromUniversalHashCode should be consistent with hashCode on non-null references") { - case class ExplicitHashCode() { override val hashCode = 42 } + class ExplicitHashCode() { override val hashCode = 42 } val hash = Hash.fromUniversalHashCode[ExplicitHashCode] - assertEquals(hash.hash(ExplicitHashCode()), 42) + assertEquals(hash.hash(new ExplicitHashCode()), 42) } test("fromUniversalHashCode should be null safe") { From 588d4004246e5e2eadcd5a4ee4b7fa340b25accd Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Sun, 10 May 2026 18:40:16 -0400 Subject: [PATCH 3/3] use properties for 3 tests --- .../src/test/scala/cats/tests/HashSuite.scala | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/shared/src/test/scala/cats/tests/HashSuite.scala b/tests/shared/src/test/scala/cats/tests/HashSuite.scala index d578844041..5b064b95ae 100644 --- a/tests/shared/src/test/scala/cats/tests/HashSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/HashSuite.scala @@ -27,6 +27,7 @@ import cats.laws.discipline.{DeferTests, MiniInt} import cats.laws.discipline.arbitrary.* import cats.laws.discipline.eq.* import cats.syntax.hash.* +import org.scalacheck.Prop class HashSuite extends CatsSuite { @@ -35,18 +36,20 @@ class HashSuite extends CatsSuite { Contravariant[Hash] } - test("hash extension method is consistent with hashCode for Int") { - assert(1.hash == 1.hashCode) + property("hash extension method is consistent with hashCode for Int") { + Prop.forAll { (n: Int) => assertEquals(n.hash, n.hashCode) } } - test("hash extension method is consistent with hashCode for String") { - assert("ABC".hash == "ABC".hashCode) + property("hash extension method is consistent with hashCode for String") { + Prop.forAll { (s: String) => assertEquals(s.hash, s.hashCode) } } - test("fromUniversalHashCode should be consistent with hashCode on non-null references") { - class ExplicitHashCode() { override val hashCode = 42 } - val hash = Hash.fromUniversalHashCode[ExplicitHashCode] - assertEquals(hash.hash(new ExplicitHashCode()), 42) + property("fromUniversalHashCode should be consistent with hashCode on non-null references") { + Prop.forAll { (n: Int) => + class ExplicitHashCode() { override val hashCode = n } + val hash = Hash.fromUniversalHashCode[ExplicitHashCode] + assertEquals(hash.hash(new ExplicitHashCode()), n) + } } test("fromUniversalHashCode should be null safe") {