// 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(...))
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 } ...
知道怎么做吗?请不要说“只使用宏”;至少在2.10,它们不够通用.
顺便说一句,我看到了我概述的方法存在两个问题:1)编译器插件采用AST,而不是闭包.它需要一些创建闭包的方法,可能会在用户代码上添加构建依赖项. 2)用户无法访问scala.reflect.internal.Trees.Tree,只能访问scala.reflect.runtime.universe.Tree,因此插件需要在两者之间进行转换.
解决方法
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 }
5)但是,好消息是你不必使用scala.reflect.runtime.universe.Tree.你可以创建依赖类型的createFooTree:def createFooTree [U<:scala.reflect.api.Universe with Singleton](u:Universe):u.Tree.这个,或者我们在Scala 2.10中使用scala.reflect.macros.Context的方法.不是很漂亮,但解决了宇宙不匹配的问题. 作为一个底线,我目前的感觉是静态类型语言中的宏(特别是在面向对象语言中,因为OO为代码片段相互依赖带来了一堆惊人的方法)真的很棘手.尚未发现用于修改正在编译的程序中的任意片段的类型宏的鲁棒模型. 如果您希望我们可以通过电子邮件进行更详细的讨论.我们还可以合作,使适当的宏的想法成为现实.或者,如果您可以共享您的用例,我可以尝试帮助您找到适合您特定情况的解决方法.