2012-12-01 14 views
9

Supponiamo che io ho questa macro:Controllo per varargs tipo attribuzione nelle macro Scala

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

object FooExample { 
    def foo[A](xs: A*): Int = macro foo_impl[A] 
    def foo_impl[A](c: Context)(xs: c.Expr[A]*) = c.literal(xs.size) 
} 

Questo funziona come previsto con "reale" varargs:

scala> FooExample.foo(1, 2, 3) 
res0: Int = 3 

Ma il comportamento con una sequenza attribuita al tipo varargs è fonte di confusione per me (in Scala 2.10.0-RC3):

scala> FooExample.foo(List(1, 2, 3): _*) 
res1: Int = 1 

E per dimostrare che nulla di sospetto sta succedendo wi il tipo desunto:

scala> FooExample.foo[Int](List(1, 2, 3): _*) 
res2: Int = 1 

Mi sarei aspettato un errore in fase di compilazione qui, ed è quello che voglio. Ho usato il seguente approccio nella maggior parte delle macro che ho scritto:

object BarExample { 
    def bar(xs: Int*): Int = macro bar_impl 
    def bar_impl(c: Context)(xs: c.Expr[Int]*) = { 
    import c.universe._ 
    c.literal(
     xs.map(_.tree).headOption map { 
     case Literal(Constant(x: Int)) => x 
     case _ => c.abort(c.enclosingPosition, "bar wants literal arguments!") 
     } getOrElse c.abort(c.enclosingPosition, "bar wants arguments!") 
    ) 
    } 
} 

E questo coglie il problema in fase di compilazione:

scala> BarExample.bar(3, 2, 1) 
res3: Int = 3 

scala> BarExample.bar(List(3, 2, 1): _*) 
<console>:8: error: bar wants literal arguments! 
       BarExample.bar(List(3, 2, 1): _*) 

Questo si sente come un trucco per me, anche se- sta mescolando un bit di validazione (controllando che gli argomenti siano letterali) con un altro (confermando che abbiamo davvero vararg). Posso anche immaginare casi in cui non ho bisogno che gli argomenti siano letterali (o dove voglio che il loro tipo sia generico).

So che potrei fare quanto segue:

object BazExample { 
    def baz[A](xs: A*): Int = macro baz_impl[A] 
    def baz_impl[A](c: Context)(xs: c.Expr[A]*) = { 
    import c.universe._ 

    xs.toList.map(_.tree) match { 
     case Typed(_, Ident(tpnme.WILDCARD_STAR)) :: Nil => 
     c.abort(c.enclosingPosition, "baz wants real varargs!") 
     case _ => c.literal(xs.size) 
    } 
    } 
} 

Ma questo è un modo brutto di gestire una molto semplice (e mi immagino ampiamente necessario) po 'di convalida degli argomenti. C'è un trucco che mi manca qui? Qual è il modo più semplice in cui posso assicurarmi che il foo(1 :: Nil: _*) nel mio primo esempio dia un errore in fase di compilazione?

+0

Quando scrivi "Mi sarei aspettato un errore in fase di compilazione qui", potresti chiarire? Ti aspetteresti che questo fosse un errore, perché questo è il requisito del tuo dominio? O questo dovrebbe essere un errore per tutti i tipi di macro vararg? –

+0

@EugeneBurmako: La mia preoccupazione è che nel caso di ascription, 'xs.head' non è in realtà un' c.Expr [A] 'a tutti-è più simile a' c.Expr [Seq [A]] '. Ecco [un paio di esempi] (https://gist.github.com/4191360). –

risposta

1

Funziona come previsto?

object BarExample { 
    def bar(xs: Int*): Int = macro bar_impl 
    def bar_impl(c: Context)(xs: c.Expr[Int]*) = { 
    import c.universe._ 
    import scala.collection.immutable.Stack 
    Stack[Tree](xs map (_.tree): _*) match { 
     case Stack(Literal(Constant(x: Int)), _*) => c.literal(x) 
     case _ => c.abort(c.enclosingPosition, "bar wants integer constant arguments!") 
    } 
    } 
} 
+0

Grazie, ma è essenzialmente uguale al mio 'BarExample', e non funzionerà nel caso generale. –

Problemi correlati