如何区分编译器推断的隐式转换与显式调用的转换?

ghi*_*hik 8 scala implicit-conversion scala-2.10 scala-macros

让我们假设将这两个等价表达式传递给Scala宏:

  • 使用编译器推断的隐式转换: 1+"foo"
  • 使用显式调用的隐式转换: any2stringadd(1)+"foo"

有没有办法在宏内区分这两个?

Tra*_*own 3

首先,这个1 + "foo"情况会很棘手,因为实际上并没有发生任何隐式转换:Int它本身确实确实有这个+方法不幸的是)。

因此,如果这是您的用例,那么您就不走运,但可以更普遍地执行您所描述的操作。我将在下面的示例中假设以下设置:

case class Foo(i: Int)
case class Bar(s: String)
implicit def foo2bar(foo: Foo) = Bar(foo.i.toString)
Run Code Online (Sandbox Code Playgroud)

首先是优雅的方法:

object ConversionDetector {
  import scala.language.experimental.macros
  import scala.reflect.macros.Context

  def sniff[A](tree: _): Boolean = macro sniff_impl[A]
  def sniff_impl[A: c.WeakTypeTag](c: Context)(tree: c.Tree) = {
    // First we confirm that the code typechecks at all:
    c.typeCheck(tree, c.universe.weakTypeOf[A])

    // Now we try it without views:
    c.literal(
      c.typeCheck(tree, c.universe.weakTypeOf[A], true, true, false).isEmpty
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

哪个按预期工作:

scala> ConversionDetector.sniff[Bar](Foo(42))
res1: Boolean = true

scala> ConversionDetector.sniff[Bar](foo2bar(Foo(42)))
res2: Boolean = false
Run Code Online (Sandbox Code Playgroud)

不幸的是,这需要非类型化宏,目前仅在Macro Paradise中可用。

你可以def在 2.10 中使用普通的旧宏来获得你想要的东西,但这有点麻烦:

object ConversionDetector {
  import scala.language.experimental.macros
  import scala.reflect.macros.Context

  def sniff[A](a: A) = macro sniff_impl[A]
  def sniff_impl[A: c.WeakTypeTag](c: Context)(a: c.Expr[A]) = {
    import c.universe._

    c.literal(
      a.tree.exists {
        case app @ Apply(fun, _) => app.pos.column == fun.pos.column
        case _ => false
      }
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

然后再次:

scala> ConversionDetector.sniff[Bar](Foo(42))
res1: Boolean = true

scala> ConversionDetector.sniff[Bar](foo2bar(Foo(42)))
res2: Boolean = false
Run Code Online (Sandbox Code Playgroud)

诀窍是在抽象语法树中查找我们看到函数应用的位置,然后检查该Apply节点及其fun子节点的位置是否具有相同的列,这表明该方法调用未显式存在于源中。