yes, the Scala 3 doc on opaque types states that
unless another overloaded == or != operator is defined for the type.
That does not make it clear if the
- overloaded operator is to be defined in the extension method or on the underlying object
- what the signature of the method should be
It does seem indeed that one can’t override the ==
in opaque types. I adapted a bit the following code from @velvetbaldmime to test it
class IntObj(val i: Int) {
override def equals(obj: Any): Boolean =
println("in IntObj.equals(obj: Any)")
false
def equals(other: IntObj): Boolean =
println("in IntObj.equals(other: IntObj)")
other.i == i
}
object Hello {
opaque type Hello = IntObj
def apply(i: Int): Hello = IntObj(i)
extension (ih: Hello)
def ==(other: Any): Boolean =
println("we are in ==(other: Any)")
other match
case io: IntObj => ih.i >= (2 * io.i)
case _ => true
def ==(other: Hello): Boolean =
println("we are in ==(other: Hello)")
ih.i >= (2 * other.i)
}
object test {
import Hello.*
def main(a: Array[String]): Unit =
val x: Hello = Hello(5)
val y: Hello = Hello(50)
val z: Hello = Hello(25)
println(y == x)
println(x == z)
}
compiling it with scala test.scala
I could then run it with scala test
to get the result wrong result, with only as output to the console
> scala test
in IntObj.equals(obj: Any)
false
in IntObj.equals(obj: Any)
false
One decompiling the source code we get for the Hello class:
public final class Hello$ implements Serializable
{
public static final Hello$ MODULE$;
private Hello$() {
}
static {
MODULE$ = new Hello$();
}
private Object writeReplace() {
return new ModuleSerializationProxy((Class)Hello$.class);
}
public IntObj apply(final int i) {
return new IntObj(i);
}
public boolean $eq$eq(final IntObj ih, final Object other) {
Predef$.MODULE$.println((Object)"we are in ==(other: Any)");
boolean b;
if (other instanceof IntObj) {
final IntObj io = (IntObj)other;
b = (ih.i() >= 2 * io.i());
}
else {
b = true;
}
return b;
}
public boolean $eq$eq(final IntObj ih, final IntObj other) {
Predef$.MODULE$.println((Object)"we are in ==(other: Hello)");
return ih.i() >= 2 * other.i();
}
}
so that shows that the ==
methods find their way into the byte code.
But they are not called from the main method, which only calls equals
.
public void main(final String[] a) {
final IntObj x = Hello$.MODULE$.apply(5);
final IntObj y = Hello$.MODULE$.apply(50);
final IntObj z = Hello$.MODULE$.apply(25);
final Predef$ module$ = Predef$.MODULE$;
final IntObj intObj = y;
final IntObj obj = x;
boolean b = false;
Label_0060: {
Label_0059: {
if (intObj == null) {
if (obj != null) {
break Label_0059;
}
}
else if (!intObj.equals(obj)) {
break Label_0059;
}
b = true;
break Label_0060;
}
b = false;
}
module$.println((Object)BoxesRunTime.boxToBoolean(b));
final Predef$ module$2 = Predef$.MODULE$;
final IntObj intObj2 = x;
final IntObj obj2 = z;
boolean b2 = false;
Label_0100: {
Label_0099: {
if (intObj2 == null) {
if (obj2 != null) {
break Label_0099;
}
}
else if (!intObj2.equals(obj2)) {
break Label_0099;
}
b2 = true;
break Label_0100;
}
b2 = false;
}
module$2.println((Object)BoxesRunTime.boxToBoolean(b2));
}
Now this is in the output from the Java version which I think is the same as for the JS.
So the problem is again that there does not seem to be a way to specify the equals method for opaque types, which seems like a pretty useful thing to be able to do.