Early initializer `new {} with SomeTrait` fails

61 views Asked by At

There seems to be a subtlety when using early initializer syntax.

trait Base { def callMe = "callMe" }
trait Proxy { this: Base => def call = s"proxied: $callMe" }

val base1 = new Base { }   // non-early init works
val baseFail = new { } with Base   // error: trait Base is abstract; cannot be instantiated
val base2 = new { val n=1 } with Base   // why does this fix the failure?
val proxy = new { } with Base with Proxy   // why doesn't this fail?

Why does the baseFail line fail, while the other vals don't?

The error message is also confusing - I'm not trying to instantiate Base, only mix it in.

2

There are 2 answers

0
Michael Zajac On BEST ANSWER

When you write new { } with Base, there technically aren't any early definitions. According to the SLS 5.1.6, the compiler looks for a pattern like this:

EarlyDefs         ::= `{' [EarlyDef {semi EarlyDef}] `}' `with'
EarlyDef          ::=  {Annotation} {Modifier} PatVarDef

Though it doesn't explicitly say what happens when the sequences of definitions is empty, it seems to just remove them, because when you compile val a = new { val x = 1 } with Base, you get something like this after the parsing phase:

val a = {
  final class $anon extends Base {
    val x = _;
    def <init>() = {
      val x = 1;
      super.<init>();
      ()
    }
  };
  new $anon()
}

But if you empty the braces, you simply get:

 val a = new Base()

Which is illegal, as is new Base.


To summarize:

Is an anonymous class, which is allowed :

val base1 = new Base { }

Simplifies to new Base, which is illegal:

val baseFail = new { } with Base

Is a proper early definition (not empty) which is allowed:

val base2 = new { val n=1 } with Base

Simplifies to new Base with Proxy which is also allowed:

val proxy = new { } with Base with Proxy

new { } with Base { } also compiles, but it's exactly the same as new Base { }, so there's no reason to write it that way.

0
Alexey Romanov On

I am not sure, but the simplest explanation I can think of for this behavior is that the empty early initializer is simply optimized away, so it's equivalent to val baseFail = new Base and val proxy = new Base with Proxy. new Base fails with the same error message, and new Base with Proxy is legal because it's an anonymous class. If so, I think it's technically a compiler bug, but quite a minor one.