Skip to content

Commit 0c538cc

Browse files
authored
Merge pull request #4169 from dotty-staging/fix/synthetic-equals
Fix #3140: Generate better `equals` for case/value classes
2 parents 5be1360 + 06541bc commit 0c538cc

File tree

3 files changed

+26
-3
lines changed

3 files changed

+26
-3
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,11 @@ class SyntheticMethods(thisPhase: DenotTransformer) {
164164
def wildcardAscription(tp: Type) = Typed(Underscore(tp), TypeTree(tp))
165165
val pattern = Bind(thatAsClazz, wildcardAscription(clazzType)) // x$0 @ (_: C)
166166
val comparisons = accessors map { accessor =>
167-
This(clazz).select(accessor).select(defn.Any_==).appliedTo(ref(thatAsClazz).select(accessor)) }
167+
This(clazz).select(accessor).equal(ref(thatAsClazz).select(accessor)) }
168168
val rhs = // this.x == this$0.x && this.y == x$0.y
169169
if (comparisons.isEmpty) Literal(Constant(true)) else comparisons.reduceLeft(_ and _)
170170
val matchingCase = CaseDef(pattern, EmptyTree, rhs) // case x$0 @ (_: C) => this.x == this$0.x && this.y == x$0.y
171-
val defaultCase = CaseDef(wildcardAscription(defn.AnyType), EmptyTree, Literal(Constant(false))) // case _ => false
171+
val defaultCase = CaseDef(Underscore(defn.AnyType), EmptyTree, Literal(Constant(false))) // case _ => false
172172
val matchExpr = Match(that, List(matchingCase, defaultCase))
173173
if (isDerivedValueClass(clazz)) matchExpr
174174
else {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class VCElideAllocations extends MiniPhase with IdentityDenotTransformer {
2929
case BinaryOp(NewWithArgs(tp1, List(u1)), op, NewWithArgs(tp2, List(u2)))
3030
if (tp1 eq tp2) && (op eq defn.Any_==) && isDerivedValueClass(tp1.typeSymbol) =>
3131
// == is overloaded in primitive classes
32-
applyOverloaded(u1, nme.EQ, List(u2), Nil, defn.BooleanType)
32+
u1.equal(u2)
3333

3434
// (new V(u)).underlying() => u
3535
case ValueClassUnbox(NewWithArgs(_, List(u))) =>

compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,27 @@ class TestBCode extends DottyBytecodeTest {
241241
assert(!hasInstanceof, "Try case should not issue INSTANCEOF opcode\n")
242242
}
243243
}
244+
245+
@Test def noBoxingInSyntheticEquals = {
246+
val source =
247+
"""
248+
|case class Case(x: Long)
249+
|class Value(val x: Long) extends AnyVal
250+
""".stripMargin
251+
252+
checkBCode(source) { dir =>
253+
for ((clsName, methodName) <- List(("Case", "equals"), ("Value$", "equals$extension"))) {
254+
val moduleIn = dir.lookupName(s"$clsName.class", directory = false)
255+
val moduleNode = loadClassNode(moduleIn.input)
256+
val equalsMethod = getMethod(moduleNode, methodName)
257+
258+
val callsEquals = instructionsFromMethod(equalsMethod).exists {
259+
case i @ Invoke(_, _, "equals", _, _) => true
260+
case i => false
261+
}
262+
263+
assert(!callsEquals, s"equals method should not be called in the definition of $clsName#$methodName\n")
264+
}
265+
}
266+
}
244267
}

0 commit comments

Comments
 (0)