@@ -60,15 +60,16 @@ private[hashing] class MurmurHash3 {
6060 finalizeHash(h, 2 )
6161 }
6262
63- /** Compute the hash of a product */
63+ // @deprecated("use `caseClassHash` instead", "2.13.17")
64+ // The deprecation is commented because this method is called by the synthetic case class hashCode.
65+ // In this case, the `seed` already has the case class name mixed in and `ignorePrefix` is set to true.
66+ // Case classes compiled before 2.13.17 call this method with `productSeed` and `ignorePrefix = false`.
67+ // See `productHashCode` in `SyntheticMethods` for details.
6468 final def productHash (x : Product , seed : Int , ignorePrefix : Boolean = false ): Int = {
6569 val arr = x.productArity
66- // Case objects have the hashCode inlined directly into the
67- // synthetic hashCode method, but this method should still give
68- // a correct result if passed a case object.
69- if (arr == 0 ) {
70- x.productPrefix.hashCode
71- } else {
70+ if (arr == 0 )
71+ if (! ignorePrefix) x.productPrefix.hashCode else seed
72+ else {
7273 var h = seed
7374 if (! ignorePrefix) h = mix(h, x.productPrefix.hashCode)
7475 var i = 0
@@ -80,6 +81,24 @@ private[hashing] class MurmurHash3 {
8081 }
8182 }
8283
84+ /** See the [[MurmurHash3.caseClassHash(x:Product,caseClassName:String) ]] overload */
85+ final def caseClassHash (x : Product , seed : Int , caseClassName : String ): Int = {
86+ val arr = x.productArity
87+ val aye = (if (caseClassName != null ) caseClassName else x.productPrefix).hashCode
88+ if (arr == 0 ) aye
89+ else {
90+ var h = seed
91+ h = mix(h, aye)
92+ var i = 0
93+ while (i < arr) {
94+ h = mix(h, x.productElement(i).## )
95+ i += 1
96+ }
97+ finalizeHash(h, arr)
98+ }
99+ }
100+
101+
83102 /** Compute the hash of a string */
84103 final def stringHash (str : String , seed : Int ): Int = {
85104 var h = seed
@@ -337,14 +356,46 @@ object MurmurHash3 extends MurmurHash3 {
337356 final val mapSeed = " Map" .hashCode
338357 final val setSeed = " Set" .hashCode
339358
340- def arrayHash [@ specialized T ](a : Array [T ]): Int = arrayHash(a, arraySeed)
341- def bytesHash (data : Array [Byte ]): Int = bytesHash(data, arraySeed)
342- def orderedHash (xs : IterableOnce [Any ]): Int = orderedHash(xs, symmetricSeed)
343- def productHash (x : Product ): Int = productHash(x, productSeed)
344- def stringHash (x : String ): Int = stringHash(x, stringSeed)
345- def unorderedHash (xs : IterableOnce [Any ]): Int = unorderedHash(xs, traversableSeed)
359+ def arrayHash [@ specialized T ](a : Array [T ]): Int = arrayHash(a, arraySeed)
360+ def bytesHash (data : Array [Byte ]): Int = bytesHash(data, arraySeed)
361+ def orderedHash (xs : IterableOnce [Any ]): Int = orderedHash(xs, symmetricSeed)
362+ def stringHash (x : String ): Int = stringHash(x, stringSeed)
363+ def unorderedHash (xs : IterableOnce [Any ]): Int = unorderedHash(xs, traversableSeed)
346364 def rangeHash (start : Int , step : Int , last : Int ): Int = rangeHash(start, step, last, seqSeed)
347365
366+ @ deprecated(" use `caseClassHash` instead" , " 2.13.17" )
367+ def productHash (x : Product ): Int = caseClassHash(x, productSeed, null )
368+
369+ /**
370+ * Compute the `hashCode` of a case class instance. This method returns the same value as `x.hashCode`
371+ * if `x` is an instance of a case class with the default, synthetic `hashCode`.
372+ *
373+ * This method can be used to implement case classes with a cached `hashCode`:
374+ * {{{
375+ * case class C(data: Data) {
376+ * override lazy val hashCode: Int = MurmurHash3.caseClassHash(this)
377+ * }
378+ * }}}
379+ *
380+ * '''NOTE''': For case classes (or subclasses) that override `productPrefix`, the `caseClassName` parameter
381+ * needs to be specified in order to obtain the same result as the synthetic `hashCode`. Otherwise, the value
382+ * is not in sync with the case class `equals` method (scala/bug#13033).
383+ *
384+ * {{{
385+ * scala> case class C(x: Int) { override def productPrefix = "Y" }
386+ *
387+ * scala> C(1).hashCode
388+ * val res0: Int = -668012062
389+ *
390+ * scala> MurmurHash3.caseClassHash(C(1))
391+ * val res1: Int = 1015658380
392+ *
393+ * scala> MurmurHash3.caseClassHash(C(1), "C")
394+ * val res2: Int = -668012062
395+ * }}}
396+ */
397+ def caseClassHash (x : Product , caseClassName : String = null ): Int = caseClassHash(x, productSeed, caseClassName)
398+
348399 private [scala] def arraySeqHash [@ specialized T ](a : Array [T ]): Int = arrayHash(a, seqSeed)
349400 private [scala] def tuple2Hash (x : Any , y : Any ): Int = tuple2Hash(x.## , y.## , productSeed)
350401
@@ -397,8 +448,13 @@ object MurmurHash3 extends MurmurHash3 {
397448 def hash (xs : IterableOnce [Any ]) = orderedHash(xs)
398449 }
399450
451+ @ deprecated(" use `caseClassHashing` instead" , " 2.13.17" )
400452 def productHashing = new Hashing [Product ] {
401- def hash (x : Product ) = productHash(x)
453+ def hash (x : Product ) = caseClassHash(x)
454+ }
455+
456+ def caseClassHashing = new Hashing [Product ] {
457+ def hash (x : Product ) = caseClassHash(x)
402458 }
403459
404460 def stringHashing = new Hashing [String ] {
0 commit comments