bug icon indicating copy to clipboard operation
bug copied to clipboard

Subclasses should not share fields with superclasses if superclass field accessor is non-final

Open scabug opened this issue 10 years ago • 4 comments

class A(val y: Int)
class B(y: Int) extends A(y) {
  def yy = y
}
class C extends B(0) {
  override val y = -1
}

object Test {
  def main(args: Array[String]): Unit = {
    println(new C().yy) // prints 0 as expected
  }
}

Compared with:

class A(val y: Int)
class B(override val y: Int) extends A(y) {
  def yy = y
}
class C extends B(0) {
  override val y = -1
}

object Test {
  def main(args: Array[String]): Unit = {
    println(new C().yy) // prints -1
  }
}

I'm sure this is a duplicate, but I couldn't find the original issue right now, and need to record this for posterity. Might not actually be a bug now that I think about it.

scabug avatar May 27 '15 01:05 scabug

Imported From: https://issues.scala-lang.org/browse/SI-9330?orig=1 Reporter: @retronym Affected Versions: 2.11.6

scabug avatar May 27 '15 01:05 scabug

@adriaanm said: I think the behavior is correct in both cases. When you make B's y a val, it becomes available for overriding.

scabug avatar May 27 '15 17:05 scabug

as of Scala 3.2.2, both examples give:

overriding val parameter value y in class B is deprecated, will be illegal in a future version

in Scala 2, I suppose we could do the same under -Xsource:3

SethTisue avatar Feb 09 '23 23:02 SethTisue

The runtime behavior is the same in for 3.2.2 and 2.13.14. In the latest 3.4.1, the deprecation is an error.

I wonder if "prints 0 as expected" is actually what we should expect.

class A(val y: Int)
class B(y: Int) extends A(y) {
  def yy = y // invokespecial A.y
}

The bytecode is super[A].y (invokespecial) because of parameter aliasing. B.y has alias A.y, the access to B.y is rewritten to A.y, which ends up in bytecode as a super call. So there's no virtual call, yy always calls the A.y accessor.

In the second example

class A(val y: Int)
class B(override val y: Int) extends A(y) {
  def yy = y // `invokevirtual B.y`
}

there's also no field generated in B, but the call to y is a virtual call to B.y (parameter aliasing replaces the field read of y in the accessor B.y by super[A].y, it doesn't change yy).

lrytz avatar May 06 '24 12:05 lrytz