here's T.scala:
trait T1 {
def x = 3
}
trait T2 extends T1 {
override def x = super.x + 1
}
let's compile it using 2.12.0-M2:
% scalac212 -Xprint:all T.scala
abstract trait T1 extends scala.AnyRef {
def /*T1*/$init$(): Unit = {
()
};
def x: Int = 3
};
abstract trait T2 extends AnyRef with T1 {
def /*T2*/$init$(): Unit = {
()
};
override def x: Int = T2.super.x.+(1)
}
so far nothing interesting except that both traits have sprouted
$init$()
methods.
abstract trait T1 extends scala.AnyRef {
def /*T1*/$init$(): Unit = {
()
};
def x: Int = 3
};
abstract trait T2 extends AnyRef with T1 {
<superaccessor> <artifact> private def super$x: Int;
def /*T2*/$init$(): Unit = {
()
};
override def x: Int = T2.this.super$x.+(1)
}
the only difference is that because T2 uses super
, this line
has been added:
<superaccessor> <artifact> private def super$x: Int;
superaccessor
and artifact
are flags; artifact
indicates it's
synthetic. (there's also a different flag called synthetic
; what's
the difference? Adriaan doesn't know.)
some minor uninteresting changes happen here (x
to x()
, AnyRef
to Object
)
the superaccessor gets private
removed and final
added.
(this is not too important, but:
this is done by makeNotPrivate
. probably it begins as private
and passes through makeNotPrivate
in order to get name-mangled,
to avoid name clashes. makeNotPrivate
also adds final
;
there are comments in that method that explain why. it isn't
really relevant here)
Erasure includes AddInterfaces, which creates implementation classes.
In the trait itself,
abstract trait T1 extends Object {
def x(): Int
};
we see T1.x
has become deferred, because the body has been moved to
the implementation class. So T1 has become an interface trait
(a
trait with no implementations).
And here's the implementation class:
abstract trait T1$class extends Object with T1 {
def /*T1$class*/$init$(): Unit = {
()
};
def x(): Int = 3
};
At this stage, the implementation class looks rather different than it
will in the final bytecode. At the moment, it is a trait that extends
T1 and contains ordinary methods; in the eventual bytecode, T1$class
will be an abstract, never-instantiated class with only static
methods.
(Why does it happen this way? Maybe it doesn't really matter? Scala doesn't have the concept of "static", but we need to encode the implementation class somehow. Since we're seeking to eliminate the implementation classes entirely, it's probably not worth worrying about.)
In this example, the $init$
doesn't do anything. We would see
something there if the trait itself had statements in its body, or if
the trait contained val
s or var
s requiring initialization.
The changes in T2 are comparable:
abstract trait T2 extends Object with T1 {
final <superaccessor> <artifact> def super$x(): Int;
override def x(): Int
};
abstract trait T2$class extends Object with T1$class with T2 {
def /*T2$class*/$init$(): Unit = {
()
};
override def x(): Int = T2$class.this.super$x().+(1)
}
Note the call to the superaccessor in the body of x()
in the
implementation class.
Nothing changes in this phase except that the order of stuff gets shuffled around.
T1 and T2 themselves remain unchanged here (except for AnyRef
being
replaced with Object
for some reason).
The implementation classes lose their superclasses:
abstract trait T1$class extends { ...
abstract trait T2$class extends { ...
this makes sense because these will eventually become abstract classes with only static methods.
that's it for trees; now the back end takes over, so let's look at the bytecode. we could use javap, but it's usually easier to read if decompiled, so we'll use cfr for that.
% foreach FOO (*.class); cfr $FOO; end
For T1 we see no substantive change:
public interface T1 {
public int x();
}
public interface T2
extends T1 {
public /* synthetic */ int T2$$super$x();
@Override
public int x();
}
the implementation classes becomes abstract class
and the methods
become static
:
public abstract class T1$class {
public static int x(T1 $this) {
return 3;
}
public static void $init$(T1 $this) {
}
}
public abstract class T2$class {
public static int x(T2 $this) {
return $this.T2$$super$x() + 1;
}
public static void $init$(T2 $this) {
}
}
and in the $init$
methods we see void
instead of Unit
.
Now let's add some additional Scala source to T.scala:
class C extends T2
after parsing, typing, and erasure, we get:
class C extends Object with T2 {
def <init>(): C = {
C.super.<init>();
C.this.$asInstanceOf[T1$class]()./*T1$class*/$init$();
C.this.$asInstanceOf[T2$class]()./*T2$class*/$init$();
()
}
};
though in our simple example the init methods in T1 and T2 don't actually do anything.
The mixin phase is where forwarders for x()
and for the
superaccessor get added. The forwarders call the static
methods in the implementation class:
class C extends Object with T2 {
final <superaccessor> <artifact> def super$x(): Int = T1$class.x(C.this);
override def x(): Int = T2$class.x(C.this);
def <init>(): C = {
C.super.<init>();
T1$class./*T1$class*/$init$(C.this);
T2$class./*T2$class*/$init$(C.this);
()
}
};
and the final bytecode is not interestingly different:
public class C implements T2 {
@Override
public /* synthetic */ int T2$$super$x() {
return T1$class.x(this);
}
@Override
public int x() {
return T2$class.x(this);
}
public C() {
T1$class.$init$(this);
T2$class.$init$(this);
}
}
artifact
vssynthetic
. things flaggedartifact
in the scala compiler will be flaggedACC_SYNTHETIC
in the classfile, see https://github.com/scala/scala/blob/2.11.x/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala#L579, and jvm spec https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html.synthetic
in the compiler is for other things that are synthesized by scalac but not markedACC_SYNTHETIC
in the classfile, like for example local variables generated in eta-expansion or names-defaults desugaring. there are some places where the compiler checks if a symbol is synthetic or not.i think (not too sure, but https://github.com/scala/scala/blob/2.11.x/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala#L804-L805 suggests so) think marked
artifact
are alsosynthetic
.there's most likely bugs in there - things marked synth that should be art etc.
See also https://github.com/scala/scala/blob/2.11.x/src/reflect/scala/reflect/api/FlagSets.scala#L233-L247.