4

I am trying to annotate the constructor values of a class using macro-annotations. Assume that a macro-annotation called @identity is implemented and is used as follows in the class definition of class A:

class A(@identity val foo: String, // causes error
        val bar: String) {
@identity val foobar: String = "" // doesn't cause error
}

When just annotating foobar everything compiles just fine. However, when annotating foo I get the following compile-time error:

top-level class without companion can only expand either into an eponymous class or into a block consisting in eponymous companions

Could someone elaborate on this error and why it occurs?

Maarten
  • 207
  • 2
  • 13

1 Answers1

3

I suspect you call a macro

  import scala.annotation.{StaticAnnotation, compileTimeOnly}
  import scala.language.experimental.macros
  import scala.reflect.macros.whitebox

  @compileTimeOnly("enable macro paradise to expand macro annotations")
  class identity extends StaticAnnotation {
    def macroTransform(annottees: Any*): Any = macro identity.impl
  }

  object identity {
    def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
      import c.universe._
      println(s"$annottees")
      q"..$annottees"
    }
  }

like

  class A(@identity val foo: String,
          val bar: String) {
    @identity val foobar: String = ""
  }

  object A

Then you have error

Warning:scalac: List(<paramaccessor> val foo: String = _, class A extends scala.AnyRef {
  <paramaccessor> val foo: String = _;
  <paramaccessor> val bar: String = _;
  def <init>(foo: String, bar: String) = {
    super.<init>();
    ()
  };
  @new identity() val foobar: String = ""
}, object A extends scala.AnyRef {
  def <init>() = {
    super.<init>();
    ()
  }
})
Warning:scalac: 
Warning:scalac: List(<paramaccessor> val foo: String = _, def <init>(foo: String, bar: String) = {
  super.<init>();
  ()
})
Warning:scalac: List(val foobar: String = "")
Error:(8, 12) top-level class with companion can only expand into a block consisting in eponymous companions
  class A(@identity val foo: String,
Error:(8, 12) foo is already defined as value foo
  class A(@identity val foo: String,
Error:(8, 12) foo  is already defined as value foo
  class A(@identity val foo: String,

The thing is that you take a class (and possibly companion object) and return not only them but also val foo so you change number/flavor of top-level definitions which is forbidden https://docs.scala-lang.org/overviews/macros/annotations.html

Top-level expansions must retain the number of annottees, their flavors and their names, with the only exception that a class might expand into a same-named class plus a same-named module, in which case they automatically become companions as per previous rule.

For example if we change the macro

   def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
      import c.universe._
      println(s"$annottees")
      q"..${annottees.tail}" // addded tail
    }

then everything will compile.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66