La seguente macro è una versione semplificata dello illTyped
di che tenta di digitare il codice fornito dall'utente come stringa. Restituisce None
se riesce e l'eccezione come Option[String]
in caso di errore.Perché questo codice digita tipicamente in Scala 2.11 e cosa posso fare al riguardo?
import scala.language.experimental.macros
import scala.reflect.macros.TypecheckException
import scala.reflect.macros.whitebox.Context
def typecheck_impl(c: Context)(code: c.Expr[String]): c.Expr[Option[String]] = {
import c.universe._
val Expr(Literal(Constant(codeStr: String))) = code
try {
c.typecheck(c.parse(codeStr))
c.Expr(q"None: Option[String]")
} catch {
case e: TypecheckException =>
c.Expr(q"Some(${ e.toString }): Option[String]")
}
}
def typecheck(code: String): Option[String] = macro typecheck_impl
Ora supponiamo che io ho una classe case Foo
. Perché è una classe case, Foo
avrà un estrattore generato automaticamente per esso, ma mettiamoci anche definire la nostra estrattore Bar
che fa la stessa cosa:
object Test {
case class Foo(i: Int, c: Char)
object Bar {
def unapply(foo: Foo): Option[(Int, Char)] = Some((foo.i, foo.c))
}
}
Ora possiamo scrivere la seguente:
scala> import Test._
import Test._
scala> val Foo(x, y) = Foo(1, 'a')
x: Int = 1
y: Char = a
scala> val Bar(x, y) = Foo(1, 'a')
x: Int = 1
y: Char = a
scala> val Foo(x, y, z) = Foo(1, 'a')
<console>:15: error: wrong number of arguments for pattern Test.Foo(i: Int,c: Char)
val Foo(x, y, z) = Foo(1, 'a')
^
scala> val Bar(x, y, z) = Foo(1, 'a')
<console>:15: error: too many patterns for object Bar offering (Int, Char): expected 2, found 3
val Bar(x, y, z) = Foo(1, 'a')
^
scala> typecheck("val Foo(x, y) = Foo(1, 'a')")
res0: Option[String] = None
scala> typecheck("val Bar(x, y) = Foo(1, 'a')")
res1: Option[String] = None
scala> typecheck("val Foo(x, y, z) = Foo(1, 'a')")
res2: Option[String] = Some(scala.reflect.macros.TypecheckException: wrong number of arguments for pattern Test.Foo(i: Int,c: Char))
Niente di tutto questo è sorprendente, le cose che penseresti potrebbero compilare compilazioni, cose che non faresti, e la nostra macro è d'accordo. Ma poi prova questo:
scala> typecheck("val Bar(x, y, z) = Foo(1, 'a')")
<macro>:1: error: too many patterns for object Bar offering (Int, Char): expected 2, found 3
val Bar(x, y, z) = Foo(1, 'a')
^
E la macro si strozza. Cambiare il blocco catch
per gestire qualsiasi vecchio throwable dà lo stesso risultato. Il codice equivalente funzionava come previsto in 2.10.
Come posso rilevare questo errore in modo che la mia macro funzioni come previsto in 2.11?
Buona domanda. Avrò bisogno di pensarci dopo ScalaDays. questo funzionerebbe per te? –
Di sicuro, Eugene, senza fretta, grazie! Mi sono imbattuto in questo in un ramo con alcune modifiche per l'estrattore 'Sized' di Shapeless ieri, ma non è affatto urgente (il ramo ha già mesi). –
https://issues.scala-lang.org/browse/SI-8719 –