scalascalac

Scala Compiler Plugin Rewrite Function Definition As A Tuple: error: not found: value scala.Tuple2


I am writing a compiler plugin to rewrite a function definition as a tuple of the function hash + function body

So the following

def f(a: Int, b: Int) : Int = (a + b) 

would turn into

val f = ("some-complex-hash", (a: Int, b: Int) => (a + b))

Let me note that, this is for a research project and will be used to integrate some variant of reversible computations into a subset of the language. I am aware that, on its own, this is a bad idea and will break a lot of things.

The documentation of compiler plug in construction seems to be rather lacking (I did go through the official guide), so I am trying to make progress looking at existing plugins such as the kind-projector

In order to understand how to represent this, I have followed the following process

  1. Reify the expression val expr = reify {....}
  2. Extract the tree val tree = expr.tree
  3. showRaw(tree)

I have done this for a function definition, tuple and a lambda, which I believe should be enough to implement this. I got the following so far:

ValDef(Modifiers(), TermName(dd.name), TypeTree(),
   Apply(
       Select(Ident("scala.Tuple2"), TermName("apply")),
          List(
              Literal(Constant(hash)),
              Function(
                  List(dd.vparamss),
                  dd.rhs
              )
          )
    )
 )

Before I even get to this, I am having trouble with expanding to any tuple at all i.e. rewrite any function as ("a", "b") which expands to the following in the REPL

Apply(Select(Ident(scala.Tuple2), TermName("apply")), List(Literal(Constant("a")), Literal(Constant("b"))))

The Problem

If I do Ident(scala.Tuple2) I get the following at compile time

overloaded method value Ident with alternatives:
[error]   (sym: FunctionRewriter.this.global.Symbol)FunctionRewriter.this.global.Ident <and>
[error]   (name: String)FunctionRewriter.this.global.Ident <and>
[error]   FunctionRewriter.this.global.Ident.type
[error]  cannot be applied to (Tuple2.type)
[error]           Select(Ident(scala.Tuple2), TermName("apply")),

If I do Ident("scala.Tuple2") (notice the string), I get the following when the plug in runs (at "run time")

<test>:2: error: not found: value scala.Tuple2
[error] object Main extends App {

I would appreciate any pointers on how to rewrite as Tuples

The Full Code:

class CompilerPlugin(override val global: Global) extends Plugin {
  val name        = "provenance-expander"
  val components  = new FunctionRewriter(global) :: Nil
}

class FunctionRewriter(val global: Global) extends PluginComponent with TypingTransformers {
  import global._
  override val phaseName = "compiler-plugin-phase"
  override val runsAfter = List("parser")
  override def newPhase(prev: Phase) = new StdPhase(prev) {
    override def apply(unit: CompilationUnit) {
      unit.body = new MyTypingTransformer(unit).transform(unit.body)
    }
  }

  class MyTypingTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {

    override def transform(tree: Tree) : Tree = tree match {
      case dd: DefDef =>
        val hash: String = "do some complex recursive hashing" 
        Apply(
          Select(Ident("scala.Tuple2"), TermName("apply")),
          List(Literal(Constant("a")), Literal(Constant("b")))
        )
      case _ => super.transform(tree)
    }
  }

  def newTransformer(unit: CompilationUnit) = new MyTypingTransformer(unit)
}

Solution

  • Thanks to @SethTisue for answering in the comments. I am writing up a answer for anybody who might face a similar issue in the future.

    As Seth mentioned, using mkTuple was the right way to go. In order to use it, you need the following import

    import global.gen._
    

    In this specific case, as originally speculated in the question, the transformation breaks a lot of things. Mainly transforming the methods injected by object and class definitions i.e. for method dispatch or init, results in malformed structures. The work around is using explicit annotations. So the final DefDef ends up looking like the following:

    case dd: DefDef =>
        if (dd.mods.hasAnnotationNamed(TypeName(typeOf[policyFunction].typeSymbol.name.toString))) {
          val hash: String = md5HashString(dd.rhs.toString())
          val tup =
            atPos(tree.pos.makeTransparent)(mkTuple(List(Literal(Constant(hash)), Function(dd.vparamss(0), dd.rhs))))
          val q = ValDef(Modifiers(), dd.name, TypeTree(), tup)
          println(s"Re-written Function: $q")
          q
        } else {
          dd
        }