Skip to content

Instantly share code, notes, and snippets.

@SethTisue
Last active February 9, 2016 05:40
Show Gist options
  • Save SethTisue/400a5acf6678f6550f0f to your computer and use it in GitHub Desktop.
Save SethTisue/400a5acf6678f6550f0f to your computer and use it in GitHub Desktop.

Trait with superaccessor

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

Parser and typer

  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.

Superaccessors

  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.)

Uncurry

some minor uninteresting changes happen here (x to x(), AnyRef to Object)

Explicitouter

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

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 vals or vars 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.

Constructors

Nothing changes in this phase except that the order of stuff gets shuffled around.

Mixin

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.

Bytecode generation

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.

What about a class?

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);
    }
}
@lrytz
Copy link

lrytz commented Oct 13, 2015

artifact vs synthetic. things flagged artifact in the scala compiler will be flagged ACC_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 marked ACC_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 also synthetic.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment