The following macro is a simplified version of Shapeless's illTyped
that tries to typecheck some code that you provide as a string. It returns None
if it succeeds, and the exception as an Option[String]
if it fails.
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
Now suppose I've got a case class Foo
. Because it's a case class, Foo
will have an extractor automatically generated for it, but let's also define our own extractor Bar
that does the same thing:
object Test {
case class Foo(i: Int, c: Char)
object Bar {
def unapply(foo: Foo): Option[(Int, Char)] = Some((foo.i, foo.c))
}
}
Now we can write the following:
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))
None of this is surprising—the stuff you'd think would compile compiles, the stuff you wouldn't doesn't, and our macro agrees. But then you try this:
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')
^
And the macro just chokes. Changing the catch
block to handle any old throwable gives the same result. The equivalent code worked as expected in 2.10.
How can I capture this error so that my macro works as expected in 2.11?
This is a bug that is now fixed in https://github.com/scala/scala/pull/3876. Let's hope the fix will get included in 2.11.2.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With