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

Scala 如何区分编译器推断的隐式转换和显式调用的转换?,scala,implicit-conversion,scala-2.10,scala-macros,Scala,Implicit Conversion,Scala 2.10,Scala Macros,让我们想象一下将这两个等效表达式传递给Scala宏: 使用编译器推断的隐式转换:1+“foo” 使用显式调用的隐式转换:any2stringadd(1)+“foo” 有没有办法在宏内部区分这两种情况?首先,1+“foo”的情况会很棘手,因为实际上没有任何隐式转换发生:Int本身() 因此,如果这是您的用例,那么您就不走运了,但是您可以做您更一般地描述的事情。我将在下面的示例中假设以下设置: case class Foo(i: Int) case class Bar(s: String) im

让我们想象一下将这两个等效表达式传递给Scala宏:

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

有没有办法在宏内部区分这两种情况?

首先,
1+“foo”
的情况会很棘手,因为实际上没有任何隐式转换发生:
Int
本身()

因此,如果这是您的用例,那么您就不走运了,但是您可以做您更一般地描述的事情。我将在下面的示例中假设以下设置:

case class Foo(i: Int)
case class Bar(s: String)
implicit def foo2bar(foo: Foo) = Bar(foo.i.toString)
首先是优雅的方法:

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
    )
  }
}
根据需要工作:

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

scala> ConversionDetector.sniff[Bar](foo2bar(Foo(42)))
res2: Boolean = false
不幸的是,这需要非类型化宏,而这些宏目前仅在中可用

在2.10版本中,使用普通的
def
宏可以得到您想要的,但这有点像黑客:

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
      }
    )
  }
}
再说一遍:

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

scala> ConversionDetector.sniff[Bar](foo2bar(Foo(42)))
res2: Boolean = false

诀窍是在抽象语法树中查找函数应用程序的位置,然后检查
Apply
节点及其
fun
子节点的位置是否具有相同的列,这表明方法调用在源代码中没有显式出现。

这是一个错误,但它可能会帮助您:

import scala.reflect.macros.Context
import language.experimental.macros

object Macros {
  def impl(c: Context)(x: c.Expr[Int]) = {
    import c.universe._
    val hasInferredImplicitArgs = x.tree.isInstanceOf[scala.reflect.internal.Trees#ApplyToImplicitArgs]
    val isAnImplicitConversion = x.tree.isInstanceOf[scala.reflect.internal.Trees#ApplyImplicitView]
    println(s"x = ${x.tree}, args = $hasInferredImplicitArgs, view = $isAnImplicitConversion")
    c.literalUnit
  }

  def foo(x: Int) = macro impl
}

import language.implicitConversions
import scala.reflect.ClassTag

object Test extends App {
  def bar[T: ClassTag](x: T) = x
  implicit def foo(x: String): Int = augmentString(x).toInt
  Macros.foo(2)
  Macros.foo(bar(2))
  Macros.foo("2")
}

08:30 ~/Projects/210x/sandbox (2.10.x)$ ss
x = 2, args = false, view = false
x = Test.this.bar[Int](2)(ClassTag.Int), args = true, view = false
x = Test.this.foo("2"), args = false, view = true

谢谢你的回答。我不知道
Int
有一个接受
String
的方法
+
,但我指的是一般情况。我也在考虑使用
Position
s,但这确实有点“黑客味”。我认为如果
Tree
s有一些标志会说“这棵树是由编译器自动推断的”,那就太酷了。我刚刚在
scala reflect
源代码中找到了一个关于
Apply
AST上的一些潜在标志,它将指示隐式转换。现在似乎有一个单独的类来表示这一点(不幸的是,它是内部的)。不幸的是,使用
Position
技巧将意味着您的代码在REPL中使用时会崩溃@ghik,我认为你最后的评论是正确的,@EugeneBurmako的回答详细阐述了这一点。