I’m working on a compiler plugin to generate a “clone” method that just needs to be able to create a fresh instance of a class with the same parameters as a given instance.
My plugin transform method is as follows:
def isBundle(sym: Symbol): Boolean =
sym.fullName == "<package>.Bundle" || sym.parentSymbolsIterator.exists(isBundle)
override def transform(tree: Tree): Tree = tree match {
case bundle: ClassDef if isBundle(bundle.symbol) =>
bundle match {
case q"""$mods class $tpname[..$tparams] $ctorMods(...$paramss)
extends { ..$earlydefns } with ..$parents { $self =>
..$stats
}""" =>
println(s"========== Matched on $tpname ==========")
println(bundle)
// We know the types but the compiler doesn't
val paramssTyped = paramss.asInstanceOf[List[List[ValDef]]]
val tparamsTyped = tparams.asInstanceOf[List[TypeDef]]
val genMethod = {
// Get refs to the Trees
val paramssRefs = paramssTyped.map(_.map(_.name))
val tparamsRefs = tparamsTyped.map { t => tq"${t.name}" }
val impl = q"override def cloneType = new $tpname[..$tparamsRefs](...$paramssRefs)"
println(impl)
// This fails
localTyper.typed(impl)
}
val ClassDef(mods0, name0, tparams0, template@Template(parents0, self0, body0)) = bundle
val templateResult = treeCopy.Template(template, parents0, self0, body0 :+ genMethod)
val result = treeCopy.ClassDef(bundle, mods0, name0, tparams0, templateResult)
// If I comment out the typing of impl, this returns but doesn't seem to fill in the types for cloneType
localTyper.typed(result)
// Some classes don't match the quasiquotes above
case _ => super.transform(tree)
}
case _ => super.transform(tree)
}
When I try testing this on a relatively simple example it fails:
class MyBundle(x: Int) extends Bundle {
val foo = Output(UInt(x.W))
}
This gives:
========== Matched on MyBundle ==========
class MyBundle extends chisel3.Bundle {
<paramaccessor> private[this] val x: Int = _;
def <init>(x: Int): MyBundle = {
MyBundle.super.<init>();
()
};
private[this] val foo: chisel3.UInt = chisel3.Output.apply[chisel3.UInt](chisel3.`package`.UInt.apply(chisel3.`package`.fromIntToWidth(MyBundle.this.x).W));
<stable> <accessor> def foo: chisel3.UInt = MyBundle.this.foo
}
override def cloneType = new MyBundle(x)
[error] ## Exception when compiling 1 sources to .../target/scala-2.12/classes
[error] scala.reflect.internal.Types$TypeError: not found: value x
I’ve also tried running the typer on the full class after adding the method to the class body, but it doesn’t seem like the typer recurses (the tree it returns doesn’t have any types filled in).
I have looked at examples like data-class
(https://github.com/alexarchambault/data-class/blob/797de5dd0cd5cf8804bca2088ceaa1c7b96f6815/src/main/scala/dataclass/Macros.scala) but I suspect that macros don’t need to invoke the typer themselves since I don’t see any use of it there.
Any tips or examples of generating a method in a compiler plugin? Thanks!