我有一个自定义类A
,并且在该类中定义了一些操作,如下所示:
def +(that: A) = ...
def -(that: A) = ...
def *(that: A) = ...
def +(that: Double) = ...
def -(that: Double) = ...
def *(that: Double) = ...
当2.0 + x
类型为x
时,为了使类似A
的东西有意义,我定义了以下隐式类:
object A {
implicit class Ops (lhs: Double) {
def +(rhs: A) = ...
def -(rhs: A) = ...
def *(rhs: A) = ...
}
}
一切正常。现在,我介绍一个带有TypingTransformer
的编译器插件,该插件可以进行一些优化。具体来说,假设我有一个ValDef
:
val x = y + a * z
其中x
,y
和z
的类型为A
,而a
是Double
。通常,这可以编译。我将其放入优化器中,该优化器使用准引用将y + a * z
更改为其他内容。 但在此特定示例中,表达式未更改(没有要执行的优化)。突然,编译器不再为a * z
进行隐式转换。
总而言之,我有一个编译器插件,该插件带有一个表达式,该表达式通常对其应用了隐式转换。它通过准引用创建新的表达式,在语法上与旧表达式相同。但是对于这个新表达式,编译器无法执行隐式转换。
所以我的问题是-编译器如何确定必须进行隐式转换? AST中是否有一个特定的标志或需要设置的参数,而准引数未能设置?
更新
插件阶段如下所示:
override def transform(tree: Tree) = tree match {
case ClassDef(classmods, classname, classtparams, impl) if classname.toString == "Module" => {
var implStatements: List[Tree] = List()
for (node <- impl.body) node match {
case DefDef(mods, name, tparams, vparamss, tpt, body) if name.toString == "loop" => {
var statements: List[Tree] = List()
for (statement <- body.children.dropRight(1)) statement match {
case Assign(opd, rhs) => {
val optimizedRHS = optimizeStatement(rhs)
statements = statements ++ List(Assign(opd, optimizedRHS))
}
case ValDef(mods, opd, tpt, rhs) => {
val optimizedRHS = optimizeStatement(rhs)
statements = statements ++
List(ValDef(mods, opd, tpt, optimizedRHS))
}
case Apply(Select(src1, op), List(src2)) if op.toString == "push" => {
val optimizedSrc2 = optimizeStatement(src2)
statements = statements ++
List(Apply(Select(src1, op), List(optimizedSrc2)))
}
case _ => statements = statements ++ List(statement)
}
val newBody = Block(statements, body.children.last)
implStatements = implStatements ++
List(DefDef(mods, name, tparams, vparamss, tpt, newBody))
}
case _ => implStatements = implStatements ++ List(node)
}
val newImpl = Template(impl.parents, impl.self, implStatements)
ClassDef(classmods, classname, classtparams, newImpl)
}
case _ => super.transform(tree)
}
def optimizeStatement(tree: Tree): Tree = {
// some logic that transforms
// 1.0 * x + 2.0 * (x + y)
// into
// 3.0 * x + 2.0 * y
// (i.e. distribute multiplication & collect like terms)
//
// returned trees are always newly created
// returned trees are create w/ quasiquotes
// something like
// 1.0 * x + 2.0 * y
// will return
// 1.0 * x + 2.0 * y
// (i.e. syntactically unchanged)
}
更新2
有关最小工作示例,请参考此GitHub存储库:https://github.com/darsnack/compiler-plugin-demo
问题是我优化语句后a * z
变成了a.<$times: error>(z)
。
答案 0 :(得分:3)
问题与与树关联的pos
字段有关。即使一切在namer
之前发生,并且带有和没有编译器插件的树在语法上都是相同的,但由于编译器源代码中的这一讨厌行,编译器将无法推断隐式转换:
val retry = typeErrors.forall(_.errPos != null) && (errorInResult(fun) || errorInResult(tree) || args.exists(errorInResult))
(要找到此内容,请贷给hrhino)。
解决方案是在创建新树时始终使用treeCopy
,以便复制所有内部标志/字段:
case Assign(opd, rhs) => {
val optimizedRHS = optimizeStatement(rhs)
statements = statements ++ List(treeCopy.Assign(statement, opd, optimizedRHS))
}
当使用准引用生成树时,请记住设置位置:
var optimizedNode = atPos(statement.pos.focus)(q"$optimizedSrc1.$newOp")
更新了我的MWP Github存储库