2013-03-19 3 views

risposta

3

Prima di tutto, il caso 1 + "foo" sta per essere difficile perché non c'è in realtà alcuna conversione implicita succedendo lì: Int stesso really, truly does have this + method (unfortunately).

Quindi sei fuori di fortuna se questo è il tuo caso d'uso, ma è possibile fare quello che stai descrivendo in generale. Si assume la seguente configurazione nei miei esempi qui sotto:

case class Foo(i: Int) 
case class Bar(s: String) 
implicit def foo2bar(foo: Foo) = Bar(foo.i.toString) 

Prima per l'elegante approccio:

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 
    ) 
    } 
} 

che funziona come desiderato:

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

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

Purtroppo questo richiede macro non tipizzati, che sono attualmente disponibili solo in Macro Paradise.

È possibile ottenere quello che vuoi con semplici vecchie def macro in 2.10, ma è un po 'di hack:

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 
     } 
    ) 
    } 
} 

E ancora:

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

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

Il trucco è quello di cercare posti dove vediamo l'applicazione della funzione nel nostro albero di sintassi astratto e quindi per verificare se le posizioni del nodo Apply e del suo fun figlio hanno la stessa colonna, il che indica che la chiamata al metodo non è esplicitamente presente nell'origine.

+0

grazie per la risposta. Non sapevo che 'Int' ha un metodo' + 'che accetta' String' ma intendevo comunque il caso generale. Stavo pensando di usare anche 'Position's, ma questo in effetti ha un odore un po '" hacky ". Penso che sarebbe bello se 'Tree's avesse qualche bandiera che dicesse" questo albero è stato automaticamente inferito dal compilatore ". – ghik

+1

Ho appena trovato un [commento] (https://github.com/scala/scala/blob/master/src/reflect/scala/reflect/internal/Trees.scala#L431) nelle fonti 'scala-reflect' che parla circa un potenziale flag su AST 'Apply' che indicherà la conversione implicita. E sembra che ora ci sia una classe separata per indicare questo (è interno, purtroppo). – ghik

+0

Sfortunatamente usare il trucco 'Position' significa che il tuo codice esplode quando usato nel REPL. @ghik, penso che tu sia sulla strada giusta con il tuo ultimo commento, che la risposta di @ EugeneBurmako elabora. –

2

Questo è un hack, ma potrebbe aiutare a:

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 
Problemi correlati