Unexpectedly retained constructor argument
Compiler version
3.6.4 and 3.7.0-RC1
Minimized code
import scala.util.boundary
import scala.annotation.constructorOnly
class Leak()(using @constructorOnly l: boundary.Label[String]) {
Seq("a", "b").foreach(_ => boundary.break("stop"))
}
Output
Compilation error
l is marked `@constructorOnly` but it is retained as a field in class Leak
Expectation
It should compile successfully, as the constructor argument shouldn't be captured.
Note
The compiler currently does capture the variable the moment it sees it passed to a lambda. Running CFR on the compiled code (when the annotation is not present), reveals:
import java.io.Serializable;
import scala.Function1;
import scala.collection.SeqOps;
import scala.collection.immutable.$colon$colon;
import scala.collection.immutable.List;
import scala.collection.immutable.Nil$;
import scala.runtime.Nothing$;
import scala.util.boundary;
import scala.util.boundary$;
public class Leak {
private final boundary.Label<String> l;
public Leak(boundary.Label<String> l) {
this.l = l;
((SeqOps)new $colon$colon<Nothing$>((Nothing$)((Object)"a"), (List<Nothing$>)new $colon$colon<Nothing$>((Nothing$)((Object)"b"), Nil$.MODULE$))).foreach((Function1<String, Object> & Serializable)_$1 -> {
throw boundary$.MODULE$.break("stop", l);
});
}
}
where one can see that this.l is indeed never used, the Lambda simply closes over the constructor argument as expected.
I'd like to clarify, the bug is not about the annotation misfiring, but the compiler violating expectation and retaining the field.
In Scala 2, the lambda uses the instance field, so perhaps this is residual behavior.
The member starts out captured. The field is dropped if it is only used from the constructor. For the lambda case, lambda construction must happen before the constructor finishes.