Skip to content

Commit ee9b034

Browse files
oderskysmarter
authored andcommitted
Fix criterion when value classes have user defined equals methods
1 parent 0f1a23e commit ee9b034

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package transform
33

44
import ast.tpd
55
import core._
6-
import Contexts._, Symbols._
6+
import Contexts._, Symbols._, Types._, Flags._, Phases._
77
import DenotTransformers._, MegaPhase._
88
import TreeExtractors._, ValueClasses._
99

@@ -23,13 +23,19 @@ class VCElideAllocations extends MiniPhase with IdentityDenotTransformer {
2323
override def runsAfter: Set[String] = Set(ElimErasedValueType.name)
2424

2525
override def transformApply(tree: Apply)(using Context): Tree =
26+
def hasUserDefinedEquals(tp: Type): Boolean =
27+
val eql = atPhase(erasurePhase) {
28+
defn.Any_equals.matchingMember(tp.typeSymbol.thisType)
29+
}
30+
eql.owner != defn.AnyClass && !eql.is(Synthetic)
31+
2632
tree match {
27-
// new V(u1) == new V(u2) => u1 == u2
33+
// new V(u1) == new V(u2) => u1 == u2, unless V defines its own equals.
2834
// (We don't handle != because it has been eliminated by InterceptedMethods)
2935
case BinaryOp(NewWithArgs(tp1, List(u1)), op, NewWithArgs(tp2, List(u2)))
30-
if (tp1 eq tp2) && (op eq defn.Any_==) &&
31-
isDerivedValueClass(tp1.typeSymbol) &&
32-
!defn.Any_equals.overridingSymbol(tp1.typeSymbol.asClass).exists =>
36+
if (tp1 eq tp2) && (op eq defn.Any_==)
37+
&& isDerivedValueClass(tp1.typeSymbol)
38+
&& !hasUserDefinedEquals(tp1) =>
3339
// == is overloaded in primitive classes
3440
u1.equal(u2)
3541

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,35 @@ class TestBCode extends DottyBytecodeTest {
976976
assert((getMethod(c, "x_$eq").access & Opcodes.ACC_DEPRECATED) != 0)
977977
}
978978
}
979+
980+
@Test def vcElideAllocations = {
981+
val source =
982+
s"""class ApproxState(val bits: Int) extends AnyVal
983+
|class Foo {
984+
| val FreshApprox: ApproxState = new ApproxState(4)
985+
| var approx: ApproxState = FreshApprox
986+
| def meth: Boolean = approx == FreshApprox
987+
|}
988+
""".stripMargin
989+
990+
checkBCode(source) { dir =>
991+
val clsIn = dir.lookupName("Foo.class", directory = false).input
992+
val clsNode = loadClassNode(clsIn)
993+
val meth = getMethod(clsNode, "meth")
994+
995+
val instructions = instructionsFromMethod(meth)
996+
val isFrameLine = (x: Instruction) => x.isInstanceOf[FrameEntry] || x.isInstanceOf[LineNumber]
997+
// No allocations of ApproxState
998+
assertSameCode(instructions.filterNot(isFrameLine), List(
999+
VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "Foo", "approx", "()I", false),
1000+
VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "Foo", "FreshApprox", "()I", false),
1001+
Jump(IF_ICMPNE, Label(7)), Op(ICONST_1),
1002+
Jump(GOTO, Label(10)),
1003+
Label(7), Op(ICONST_0),
1004+
Label(10), Op(IRETURN)
1005+
))
1006+
}
1007+
}
9791008
}
9801009

9811010
object invocationReceiversTestCode {

0 commit comments

Comments
 (0)