-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix ==
for value classes
#4490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix ==
for value classes
#4490
Conversation
In Scala 2, overriding |
But we currently allow it, and I think it would be good to keep doing that! The semantics looks good, as far as the test cases tell us. It does not rewrite to the extension method currently, but even if we do that we'd still have one boxing left. Here's the output of the test case for
vs
It's probably easy to make the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this deserves further thought.
Beyond the semantic changes discussed below, the supported use case adds one more case that triggers boxing for value classes, which also seems contentious. That boxing is only needed because calling equals
requires upcasting to Any
(and boxing) so that the body can use a pattern match to unbox the argument and actually compare contents.
All this is avoided by sticking with extension methods.
@@ -8,7 +8,7 @@ object Test { | |||
def main(args: Array[String]): Unit = { | |||
val b1 = new Bippy1(71) | |||
val b2 = new Bippy2(71) | |||
assert(b1 == b1) | |||
assert(b1 != b1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The semantics looks good, as far as the test cases tell us.
Unfortunately overriding equals
was only forbidden in value classes but not in universal traits, so this PR appears to silently change runtime behavior for code that works right now with Scalac.
It seems necessary to consider and discuss implications for migration. Not changing behavior for now seems safer.
assert(b1 != b2)
also succeeds on Scalac, but just because the involved classes are different.
Instead, adding a test like trait Foo extends Any { override def equals(x: Any) = true }
would confirm the semantic change with Scalac. To wit:
scala> trait Foo extends Any { override def equals(x: Any) = false }
defined trait Foo
scala> trait Ding extends Any { override def hashCode = -1 }
defined trait Ding
scala> class Bippy1(val x: Int) extends AnyVal with Foo { } // warn
defined class Bippy1
scala> class Bippy2(val x: Int) extends AnyVal with Ding { } // warn
defined class Bippy2
scala> val b1 = new Bippy1(71)
b1: Bippy1 = Bippy1@47
scala> val b2 = new Bippy2(71)
b2: Bippy2 = Bippy2@47
scala> b1 == b2
<console>:14: warning: Bippy1 and Bippy2 are unrelated: they will never compare equal
b1 == b2
^
res0: Boolean = false
scala> trait Foo extends Any { override def equals(x: Any) = true }
defined trait Foo
scala> class Bippy1(val x: Int) extends AnyVal with Foo { } // warn
defined class Bippy1
scala> val b1 = new Bippy1(71)
b1: Bippy1 = Bippy1@47
scala> b1 == b2
<console>:14: warning: Bippy1 and Bippy2 are unrelated: they will never compare equal
b1 == b2
^
res1: Boolean = false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the previous equals
behavior was unintentional and non-sensical. So I don't think we need to worry about migration, that should count as a bugfix. As far as I can tell, the boxing behavior is also the same as it was before. So I don't see a downside for doing this.
Boxing could be improved for value classes that define equals
, but that's a separate issue for which @smarter would be the best person.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the previous equals behavior was unintentional and non-sensical. So I don't think we need to worry about migration, that should count as a bugfix.
Only making more programs compile is a safe bugfix, changing behavior of existing programs isn't safe. As you pointed out other times, they might depend on it without even knowing. We'd need to forbid the scenario in Scala 2 to make this safe.
And this behavior wasn't even accidental — you, Paul and Adriaan discussed it in scala/bug#6534 and scala/scala#1526, so it can't be that nonsensical, and it sure isn't a bugfix.
That -Xlint
warning for class Bippy1(val x: Int) extends AnyVal with Foo { }
was even removed in
scala/scala@1da48a4#diff-90c2f5c65d04e4b756b9989ff2e1b97dR339, but apparently only because there was no way to suppress it.
newElems(elems.length) = x | ||
multi[U](newElems) | ||
case elem: T @unchecked => | ||
Lst(elem, x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In scala2 head +: tail
has the same behaviour as head :: tail
. This implementation looks like init :+ last
.
I suspect this method should be named :+
.
We should not optimize new V(x) == new V(y) to x == y if value class `V` redefines `equals`.
Try both sides of ==. Try directly implemented as well as inherited methods.
1. Fix inline accessors for static methods 2. Disable variance checks for inline accessors
@allanrenucci @smarter There are more fixes to inline accessors in this commit which should go into the next release. The fixes are here because the test case is the latest I believe this PR is ready to go in. The only objection is that If not we can also pull out the inline fixes and add another test that exercises them. But I won't do that, too much other things to do. In any case we need to act on this before the next release is cut. |
I'm OK with a non-optimized implementation but @Blaisorblade objection that this silently changes semantics compared to Scala 2 is more serious. For now I'm in favour of just pulling the inline fixes in another PR. |
I don't believe the semantics change is worrisome. The previous behavior was that an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Let's go with this. Meanwhile on the Scala 2 side it'd be great if the warning was enabled by default: scala/scala@1da48a4#diff-90c2f5c65d04e4b756b9989ff2e1b97dR339
We should not optimize
if value class
V
redefinesequals
.This was found when further refining the
Lst
type, so that==
is now supported.