Skip to content

Commit 1ca4139

Browse files
committed
Compare productPrefix in synthetic case class canEqual
Since 2.13, case class `hashCode` mixes in the hash code of the `productPrefix` string. The synthetic `equals` method has fallen out of sync in that regard, so two instances can be equal but have different hash codes. This commit changes the synthetic `canEqual` method to also compare the `productPrefix` of the two instances.
1 parent 6e852d2 commit 1ca4139

File tree

2 files changed

+31
-1
lines changed

2 files changed

+31
-1
lines changed

compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,13 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
419419
*
420420
* `@unchecked` is needed for parametric case classes.
421421
*/
422-
def canEqualBody(that: Tree, span: Span): Tree = that.isInstance(AnnotatedType(clazzType, Annotation(defn.UncheckedAnnot, span)))
422+
def canEqualBody(that: Tree, span: Span): Tree = {
423+
val clazzTypeU = AnnotatedType(clazzType, Annotation(defn.UncheckedAnnot, span))
424+
that.isInstance(clazzTypeU).and(
425+
This(clazz).select(defn.Product_productPrefix)
426+
.select(defn.Any_==)
427+
.appliedTo(that.cast(clazzTypeU).select(defn.Product_productPrefix)))
428+
}
423429

424430
symbolsToSynthesize.flatMap(syntheticDefIfMissing)
425431
}

tests/run/t13033.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
case class C(x: Int)
2+
class D extends C(1) { override def productPrefix = "D" }
3+
4+
abstract case class C1(a: Int)
5+
class C2(a: Int) extends C1(a) { override def productPrefix = "C2" }
6+
class C3(a: Int) extends C1(a) { override def productPrefix = "C3" }
7+
8+
case class VCC(x: Int) extends AnyVal
9+
10+
object Test extends App {
11+
val c = C(1)
12+
assert(c != new D)
13+
assert(c == C(1))
14+
assert(!c.canEqual(new D))
15+
16+
val c2 = new C2(1)
17+
val c3 = new C3(1)
18+
assert(c2 != c3)
19+
assert(c2.hashCode != c3.hashCode)
20+
assert(!c2.canEqual(c3))
21+
22+
assert(VCC(1).canEqual(VCC(1)))
23+
assert(!VCC(1).canEqual(1))
24+
}

0 commit comments

Comments
 (0)