I have the following macro:
package macros
import scala.reflect.macros.blackbox.Context
object CompileTimeAssertions {
def mustBeCaseClass[T]: Unit =
macro CompileTimeAssertionsImpl.mustBeCaseClass[T]
}
object CompileTimeAssertionsImpl {
def mustBeCaseClass[T: c.WeakTypeTag](c: Context): c.Expr[Unit] = {
import c.universe._
val symbol = c.weakTypeTag[T].tpe.typeSymbol
if (!symbol.isClass || !symbol.asClass.isCaseClass) {
c.error(c.enclosingPosition, s"${symbol.fullName} must be a case class")
}
reify(Unit)
}
}
It works when generics aren't involved, but fails when they are:
import macros.CompileTimeAssertions._
import org.scalatest.{Matchers, WordSpec}
case class ACaseClass(foo: String, bar: String)
class NotACaseClass(baz: String)
class MacroSpec extends WordSpec with Matchers {
"the mustBeCaseClass macro" should {
"compile when passed a case class" in {
mustBeCaseClass[ACaseClass]
}
"not compile when passed a vanilla class" in {
// mustBeCaseClass[NotACaseClass] // fails to compile as expected.
}
"compile when working with generics" in {
// class CaseClassContainer[T] { mustBeCaseClass[T] } // fails to compile.
// new CaseClassContainer[ACaseClass]
}
}
}
The compiler error is mine:
MacroSpec.CaseClassContainer.T must be a case class
I'd like to find out what T is when the CaseClassContainer is instantiated. Is that even possible? If it is can you provide an example?
Thanks in advance.
Thanks to Eugene and Travis' advice I was able to solve this problem with type classes. Here's the solution:
package macros
import scala.reflect.macros.blackbox.Context
trait IsCaseClass[T]
object IsCaseClass {
implicit def isCaseClass[T]: IsCaseClass[T] =
macro IsCaseClassImpl.isCaseClass[T]
}
object IsCaseClassImpl {
def isCaseClass[T]
(c: Context)
(implicit T: c.WeakTypeTag[T]): c.Expr[IsCaseClass[T]] = {
import c.universe._
val symbol = c.weakTypeTag[T].tpe.typeSymbol
if (!symbol.isClass || !symbol.asClass.isCaseClass) {
c.abort(c.enclosingPosition, s"${symbol.fullName} must be a case class")
} else {
c.Expr[IsCaseClass[T]](q"_root_.macros.IsCaseClassImpl[$T]()")
}
}
}
case class IsCaseClassImpl[T]() extends IsCaseClass[T]
And here is the usage:
import macros.IsCaseClass
import org.scalatest.{Matchers, WordSpec}
case class ACaseClass(foo: String, bar: String)
class NotACaseClass(baz: String)
class CaseClassContainer[T: IsCaseClass]
class MacroSpec extends WordSpec with Matchers {
"the code" should {
"compile" in {
new CaseClassContainer[ACaseClass]
}
"not compile" in {
// new CaseClassContainer[NotACaseClass]
}
}
}
Worth noting the use of abort instead of error. Abort returns Nothing whereas error returns Unit. The latter was fine when the macro wasn't returning anything.
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