-
Notifications
You must be signed in to change notification settings - Fork 14
Description
Dotty has implemented a new encoding for trait constructors. Scala 2.14 and 3.0 should align on this implementation detail.
The alignment could come from:
- Scala 2.14 adopting the new encoding
- Scala 3.x reverting to the old encoding
- Scala 3.x modifying the new encoding to allay concerns about binary fragility (discussion below), and Scala 2.x moving to the modified version.
Sample
trait T {
println("1")
val field1 = 1
println("2")
var field2 = 2
println("3")
}
class C extends T
Scala 2.13
public class C
implements T {
private int field1;
private int field2;
@Override
public int field1() {
return this.field1;
}
@Override
public int field2() {
return this.field2;
}
@Override
public void field2_$eq(int x$1) {
this.field2 = x$1;
}
@Override
public void T$_setter_$field1_$eq(int x$1) {
this.field1 = x$1;
}
public C() {
T.$init$(this);
Statics.releaseFence();
}
}
public interface T {
public void T$_setter_$field1_$eq(int var1);
public int field1();
public int field2();
public void field2_$eq(int var1);
public static void $init$(T $this) {
Predef$.MODULE$.println((Object)"1");
$this.T$_setter_$field1_$eq(1);
Predef$.MODULE$.println((Object)"2");
$this.field2_$eq(2);
Predef$.MODULE$.println((Object)"3");
}
}
Scala 3.x
public class C
implements T {
private final int field1 = T.super.initial$field1();
private int field2 = T.super.initial$field2();
public C() {
T.super.$init$();
}
@Override
public int field1() {
return this.field1;
}
@Override
public int field2() {
return this.field2;
}
@Override
public void field2_$eq(int x$1) {
this.field2 = x$1;
}
}
public interface T {
default public void $init$() {
Predef$.MODULE$.println((Object)"3");
}
public int field1();
default public int initial$field1() {
Predef$.MODULE$.println((Object)"1");
return 1;
}
public int field2();
default public int initial$field2() {
Predef$.MODULE$.println((Object)"2");
return 2;
}
public void field2_$eq(int var1);
}
Advantages of new encoding
- eager vals may be represented by JVM
final
fields. Scala 2's isn't able to do this because final fields assignments must appear lexically in the class constructor.
Binary fragility
Traits have a number of well-known binary fragilities. Most notably, when a field is added to a trait and a subclass is not recompiled, a LinkageError
will happen when the trait constructor calls the trait setter.
The new encoding introduces new binary fragilities, one of which has a soft failure mode (execution order incorrect). This is because the subclass has the order of the fields "baked in" to its constructor which calls the intiaial$...
methods sequentially.
Following are some failure modes when certain changes are made to the trait without recompiling the subclass.
Edit | New Behaviour | Old Behaviour |
---|---|---|
Fields or side effects reordered | The subclass constructor will execute the old sequence of initialiation/effects. This could be benign but is dangerous in general | No need to recompile subclass, semantics okay |
Field deleted | LinkageError |
wasted field in the subclass, but semantically okay |
Modified, fail-fast encoding
@smarter Has suggested that we could encode the index of the initializer into the initial$...
method names.
Edit | New Behaviour |
---|---|
Fields or side effects reordered | LinkageError |
Field deleted | LinkageError |