Inject synthetic classes into a Scala package using SyntheticMembersInjector API

Answered

Given annotated classes in a regular Scala package, e.g.

package my.foo.bar

@MyAnno
class MyClass(x: String)

Annotation macro `@MyAnno` expands this to

package my.foo.bar

class MyClass private (x: String)

object MyClass {
def apply(x: String): MyClass = /* fancy factory code goes here */
...
}

I've tried injecting the companion object using my own injector that extends `SyntheticMembersInjector` and overrides `injectMembers` (I also tried `injectInners`):

override def injectMembers(source: ScTypeDefinition): Seq[String] = {
source match {
case clazz: ScClass if clazz.findAnnotationNoAliases("MyAnno") != null =>
val companionObject =
s"""|object ${clazz.getName} {
| def apply(name: String): ${clazz.getName} = ???
|}""".stripMargin

Seq(companionObject)

case _ =>
Seq.empty
}
}

However, the synthesised companion object appears to end up as an inner object of the annotated class — at least that's what IntelliJs highlighting and auto-completion behaviour suggests, corresponding to the following code:

package my.foo.bar

class MyClass(x: String) {
object MyClass {
def apply(x: String): MyClass = /* fancy factory code goes here */
...
}
}

Question: How can I inject the companion object into the same scope as the annotated class is declared in, i.e. here into package `my.foo.bar`?

2 comments
Comment actions Permalink

Sorry for the quick follow-up post, but I managed to work it out for the special case of companion objects:

override def needsCompanionObject(source: ScTypeDefinition): Boolean =
source.findAnnotationNoAliases("MyAnno") != null

override def injectFunctions(source: ScTypeDefinition): Seq[String] = {
source match {
case obj: ScObject =>
obj.fakeCompanionClassOrCompanionClass match {
case clazz: ScTypeDefinition if clazz.findAnnotationNoAliases("MyAnno") != null =>
Seq(s"""def apply(name: String): ${clazz.getName} = ???""")

case _ => Seq.empty
}

case _ => Seq.empty
}
}

However, if I wanted to inject additional members into the surrounding package, e.g. synthesise new classes, how would I go about that?

0
Comment actions Permalink

Yes, this is the correct way of injecting new members into companion objects.

As for your second question, injection is only possible within the annotated scope, and while technically scala reflect macros allows doing this, it's considered a poor practice leading to hard to debug issues and undefined behaviour. A better alternative would be to either synthesise inner classes or annotate enclosing package object directly.

1

Please sign in to leave a comment.