Dotty: unused private field is removed by dotty

Created on 20 Mar 2020  路  6Comments  路  Source: lampepfl/dotty

minimized code

class C {
  private[this] var x: Int = _
}
object Test {
  def main(args: Array[String]): Unit = {
    val field = classOf[C].getDeclaredField("x")
    println(field)
  }
}

Runtime output in dotty

[error] java.lang.NoSuchFieldException: x
[error]     at java.base/java.lang.Class.getDeclaredField(Class.java:2411)
[error]     at Test$.main(Main.scala:7)
[error]     at Test.main(Main.scala)
[error]     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error]     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error]     at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error]     at java.base/java.lang.reflect.Method.invoke(Method.java:566)

Runtime output in scala 2

It print:

private int C.x

expectation

I don't know if this is an expected behaviour or a bug?

question

All 6 comments

@ohze did you encounter an issue because of this behaviour? In the case you show, we can tell that the field is never accessed by "normal" code, so removing it seems reasonable. OTOH, I understand that this can be problematic if the field is only ever accessed with reflection, and we could consider adding an annotation which prevents the field from being removed.

The behavior is intentional. The use case is where you define a private val that is only used in the primary constructor. E.g.

class C(x: Int, y: Int):
  private val d = gcd(x, y)
  val normX = x/d
  val normY = y/d

In this case, you'd like the field not to be retained.

If the field must be retained at runtime, make it private[C].

Inferred private[this] suggests a different idiom is needed for explicit field. Private reference by companion suffices but is verbose. Maybe presence of explicit type to signal retention? (by loose analogy to constant value.) The other analogy is explicit type for API, where the client in this case is by reflection.

There's already an @transient annotation so having a dual @retained could make sense. This annotation could also be used in objects to mark fields that should actually be serialized as I proposed in https://github.com/lampepfl/dotty/issues/5135

Changed scala 2 behavior at https://github.com/scala/scala/pull/9226

Was this page helpful?
0 / 5 - 0 ratings

Related issues

odersky picture odersky  路  66Comments

japgolly picture japgolly  路  55Comments

LPTK picture LPTK  路  35Comments

felixmulder picture felixmulder  路  120Comments

odersky picture odersky  路  60Comments