将闭包传递给Scala编译器插件

emc*_*sen 6 scala scalac scala-compiler

我正在尝试编写一个Scala编译器插件,它允许非常通用的代码生成:类似于C预处理器的通用性,但更多类型安全(我不确定这是否是一个糟糕的想法,但它是一个有趣的练习).我理想的用例看起来像这样:

// User code. This represents some function that might take some args
// and outputs an abstract syntax tree.
def createFooTree(...): scala.reflect.runtime.universe.Tree = ...

// Later user code (maybe separate compilation?). Here the user generates
// code programmatically using the function call to |createFooTree| and inserts
// the code using insertTree.
insertTree(createFooTree(...))
Run Code Online (Sandbox Code Playgroud)

重要的插件代码可能如下所示(基于):

class InsertTreeComponent(val global: Global)
  extends PluginComponent
  with TypingTransformers {
  import global._
  import definitions._

  override val phaseName = "insertTree"

  override val runsRightAfter = Some("parser")
  override val runsAfter = runsRightAfter.toList
  override val runsBefore = List[String]("typer")

  def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
    def apply(unit: CompilationUnit) {
      val onTransformer = new TypingTransformer(unit) {
        override def transform(tree: Tree): Tree = tree match {
          case orig @ Apply(
            function,
            // |treeClosure| is the closure we passed, which should
            // evaluate to a Tree (albeit a runtime Tree).
            // The function.toString bit matches anything that looks like a
            // function call with a function called |insertTree|.
            treeClosure) if (function.toString == "insertTree") => {
            // This function evaluates and returns the Tree, inserting it
            // into the call site as automatically-generated code.
            // Unfortunately, the following line isn't valid.
            eval(treeClosure): Tree
          }   
  ...
Run Code Online (Sandbox Code Playgroud)

知道怎么做吗?请不要说"只使用宏"; 至少在2.10,它们不够通用.

顺便说一句,我看到了我概述的方法存在两个问题:1)编译器插件采用AST,而不是闭包.它需要一些创建闭包的方法,可能会在用户代码上添加构建依赖项.2)用户无法访问scala.reflect.internal.Trees.Tree,只能访问scala.reflect.runtime.universe.Tree,因此插件需要在两者之间进行转换.

Eug*_*ako 9

您面临的实施困难部分是2.10中的宏不够通用的原因.他们看起来非常具有挑战性,甚至是根本性的,但我很乐观他们最终会被击败.以下是一些棘手的设计问题:

1)你怎么知道你所说的功能是对的insertTree?如果用户编写了自己的名为insertTree- 如何将魔术调用与特殊功能区分开来,以及对用户定义函数的正常调用怎么办?确保您需要检查对该函数的引用.但这并不容易(见下文).

2)你究竟如何评价这个createFooTree(...)电话?和以前一样,你需要对createFooTree部件进行检查以找出它代表什么,这并不容易.

3)然后还有一个问题.如果createFooTree在您正在编译的其中一个文件中定义了什么?然后你会以某种方式将它和它的依赖项与程序的其余部分分开,将它放入不同的编译运行,编译然后调用它.然后,如果函数或其中一个依赖项的编译导致宏扩展,这应该改变编译器的某些全局状态.我们如何将它传播到程序的其余部分?

4)我一直在谈论类型检查.那是问题吗?显然,是的.如果您的宏可以扩展到任何地方,那么类型检查变得非常棘手.例如,你如何检查这个:

class C {
  insertTree(createFoo(bar)) // creates `def foo = 2`, requires `bar` to be defined to operate
  insertTree(createBar(foo)) // creates `def bar = 4`, requires `foo` to be defined to operate
}
Run Code Online (Sandbox Code Playgroud)

5)但是,好消息是你不必使用scala.reflect.runtime.universe.Tree.你可以createFooTree依赖键入:def createFooTree[U <: scala.reflect.api.Universe with Singleton](u: Universe): u.Tree.这个,或者scala.reflect.macros.Context我们在Scala 2.10中使用的方法.不是很漂亮,但解决了宇宙不匹配的问题.

作为一个底线,我目前的感觉是静态类型语言中的宏(特别是在面向对象语言中,因为OO为代码片段相互依赖带来了一堆惊人的方法)真的很棘手.尚未发现用于修改正在编译的程序中的任意片段的类型宏的鲁棒模型.

如果您希望我们可以通过电子邮件进行更详细的讨论.我们还可以合作,使适当的宏的想法成为现实.或者,如果您可以共享您的用例,我可以尝试帮助您找到适合您特定情况的解决方法.