I'm trying to write a compiler plugin that would inject a call to a base class method in a trait in Scala. For the following input source:
class Component {
def valCallback[T](ref: T, name: String): T = ???
}
trait MyTrait {
this: Component =>
val v = 5
}
I want the transformation result to look like:
trait MyTrait {
this: Component =>
val v = valCallback(5, "v")
}
This is what I've got so far:
object ValCallbackTransformer extends Transformer {
override def transform(tree: global.Tree): global.Tree = {
val transformed = super.transform(tree)
transformed match {
case cd: ClassDef if cd.mods.hasFlag(Flag.TRAIT) =>
// ... check of self type omitted ...
val clazz = cd.impl.self.tpt.tpe.parents.find(_.toString == "Component").get.typeSymbol
val func = clazz.tpe.members.find(_.name.toString == "valCallback").get
val body = cd.impl.body.map {
case vd: ValDef if !vd.mods.isParamAccessor && vd.rhs.nonEmpty =>
val lit = Literal(Constant(vd.getterName.toString))
val thiz = This(clazz)
val sel = Select(thiz, func)
val appl = Apply(sel, List(vd.rhs, lit))
thiz.tpe = clazz.tpe
sel.tpe = func.tpe
appl.tpe = definitions.UnitTpe
lit.setType(definitions.StringTpe)
treeCopy.ValDef(vd, vd.mods, vd.name, vd.tpt, appl)
case e => e
}
val impl = treeCopy.Template(cd.impl, cd.impl.parents, cd.impl.self, body)
treeCopy.ClassDef(cd, cd.mods, cd.name, cd.tparams, impl)
case other => transformed
}
}
}
However, the compiler complains with the following:
Internal error: unable to find the outer accessor symbol of trait MyTrait
[error] val v = 5
What's an outer accessor of a trait and why can't the compiler find it? How should I construct the Apply
correctly?
UPDATE: I managed to get it working (see answer posted), but it's a bit confusing why I have to invoke the method on a this pointer from the trait.
It seems like I need to Select
the method with This(<trait type>)
. However, since the function itself is not present inside the trait class, I need to find it separately in the self type. Working version:
val clazz = cd.impl.symbol.owner
val valCallbackClazz = cd.impl.self.tpt.tpe.parents.find(_.toString == "Component").get.typeSymbol
val func = valCallbackClazz.tpe.members.find(_.name.toString == "valCallback").get
val body = cd.impl.body.map {
// ....
val thiz = This(clazz)
val sel = Select(thiz, func)
// ...
}