Skip to content
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

Change in semantics for AnyVal between Scala 2 and Scala 3 #22493

Open
hamzaremmal opened this issue Feb 1, 2025 · 3 comments
Open

Change in semantics for AnyVal between Scala 2 and Scala 3 #22493

hamzaremmal opened this issue Feb 1, 2025 · 3 comments
Assignees

Comments

@hamzaremmal
Copy link
Member

Compiler version

590691b

Minimized code

trait Foo extends Any {
    override def equals(that: Any): Boolean = ???
}
class Bar(val self: Short) extends AnyVal with Foo

Output

Scala 3

[[syntax trees at end of MegaPhase{dropOuterAccessors, dropParentRefinements, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}]] // playground.scala
package <empty> {
  @SourceFile("playground.scala") trait Foo() extends Object {
    override def equals(that: Object): Boolean = ???()
  }
  @SourceFile("playground.scala") final class Bar extends Object, Foo {
    def <init>(self: Short): Unit =
      {
        this.self = self
        super()
        ()
      }
    override def equals(that: Object): Boolean = super[Foo].equals(that)
    override def hashCode(): Int = Bar.hashCode$extension(this.self())
    private val self: Short
    def self(): Short = this.self
  }
  @SourceFile("playground.scala") final module class Bar extends Object {
    def <init>(): Unit =
      {
        super()
        ()
      }
    private def writeReplace(): Object =
      new scala.runtime.ModuleSerializationProxy(classOf[Bar])
    final def hashCode$extension($this: Short): Int =
      Short.box($this).hashCode()
  }
  final lazy module val Bar: Bar = new Bar()
}
[[syntax trees at end of                  genBCode]] // playground.scala: unchanged since MegaPhase{dropOuterAccessors, dropParentRefinements, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}

Scala 2

[[syntax trees at end of                   cleanup]] // playground.scala
package <empty> {
  abstract trait Foo extends Object {
    override def equals(that: Object): Boolean = scala.Predef.???();
    def /*Foo*/$init$(): Unit = {
      ()
    }
  };
  final class Bar extends Object with Foo {
    <paramaccessor> private[this] val self: Short = _;
    <stable> <accessor> <paramaccessor> def self(): Short = Bar.this.self;
    override <synthetic> def hashCode(): Int = Bar.hashCode$extension(Bar.this.self());
    override <synthetic> def equals(x$1: Object): Boolean = Bar.equals$extension(Bar.this.self(), x$1);
    def <init>(self: Short): Bar = {
      Bar.this.self = self;
      Bar.super.<init>();
      Bar.super./*Foo*/$init$();
      ()
    }
  };
  <synthetic> object Bar extends Object {
    final <synthetic> def hashCode$extension($this: Short): Int = java.lang.Short.hashCode($this);
    final <synthetic> def equals$extension($this: Short, x$1: Object): Boolean = {
  case <synthetic> val x1: Object = x$1;
  case5(){
    if (x1.$isInstanceOf[Bar]())
      matchEnd4(true)
    else
      case6()
  };
  case6(){
    matchEnd4(false)
  };
  matchEnd4(x: Boolean){
    x
  }
}.&&({
      <synthetic> val Bar$1: Short = x$1.$asInstanceOf[Bar]().self();
      $this.==(Bar$1)
    });
    def <init>(): Bar.type = {
      Bar.super.<init>();
      ()
    }
  }
}

[[syntax trees at end of                delambdafy]] // playground.scala: tree is unchanged since cleanup
[[syntax trees at end of                       jvm]] // playground.scala: tree is unchanged since cleanup

Expectation

As the specification; SIP-15, explains, Value Classes are implicitly assumed to have structural equality and hash codes.

Notes

Same problem with the hashCode method.

@hamzaremmal hamzaremmal added itype:bug area:value-classes Issues tied to value classes. labels Feb 1, 2025
@hamzaremmal hamzaremmal self-assigned this Feb 1, 2025
@hamzaremmal hamzaremmal added the Spree Suitable for a future Spree label Feb 1, 2025
@sjrd
Copy link
Member

sjrd commented Feb 3, 2025

This is annoying. It's a breaking change compared to Scala 2. But fixing it would be a breaking change wrt. Scala 3... There isn't a clear-cut answer here. It goes against the spec on the one hand. But on the other hand, it's consistent with what happens for case classes, for which the structural equality notion is at least as important as for value classes.

@SethTisue
Copy link
Member

relevant Scala 2 ticket: scala/bug#6534

@hamzaremmal
Copy link
Member Author

In today's Core Meeting, it was decided that the semantic introduced in Scala 3 will remain as is. This decision is aligned with the decision for #22494, where allowing this and forbidding the other will create a workaround to defining custom equals and hashCode methods.
I will leave this ticket open for now until we change the specification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants