【问题标题】:How does the Scala compiler perform implicit conversion?Scala 编译器如何进行隐式转换?
【发布时间】:2019-09-04 22:39:28
【问题描述】:

我有一个自定义类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

其中xyzA 类型,而aDouble。通常,这编译得很好。我把它通过优化器,它使用准引号将y + a * z 更改为其他内容。 但是在这个特定的例子中,表达式没有改变(没有要执行的优化)。突然,编译器不再对a * z 进行隐式转换。

总而言之,我有一个编译器插件,它采用通常会应用隐式转换的表达式。它通过准引号创建一个新表达式,在语法上与旧表达式相同。但是对于这个新的表达式,编译器无法进行隐式转换。

所以我的问题 - 编译器如何确定必须进行隐式转换?是否需要在 AST 中设置一个特定的标志或某些东西,而 quasiquotes 无法设置?


更新

插件阶段如下所示:

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.&lt;$times: error&gt;(z)

【问题讨论】:

  • 这个插件在什么阶段工作? docs.scala-lang.org/overviews/plugins/index.html
  • 您能否提供插件代码(或其简化版本仍然存在这种错误行为)?
  • 不幸的是,插件/自定义类型/等。代码库太大,无法在此处轻松显示。这就是为什么我一直在努力寻找一个最低限度的工作示例。我将更新我的问题以提供更多详细信息。
  • 这可能取决于optimizeStatement。使用微不足道的 def optimizeStatement(tree: Tree): Tree = tree 隐式转换编译 github.com/DmytroMitin/compiler-plugin-demo
  • 是的,它确实与我如何生成优化语句有关。我很难理解为什么。我已经分叉了您的回购并创建了该问题的最低工作示例。你介意看看吗?

标签: scala scalac scala-compiler scala-quasiquotes


【解决方案1】:

该问题与与树关联的 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 存储库:https://github.com/darsnack/compiler-plugin-demo

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多