如何在 Scala 的编译时检查某些 T 是否为 case class?
How to check if some T is a case class at compile time in Scala?
我有以下宏:
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)
}
}
它在不涉及泛型时有效,但在涉及以下情况时失败:
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]
}
}
}
编译错误是我的:
MacroSpec.CaseClassContainer.T must be a case class
我想知道实例化 CaseClassContainer 时 T 是什么。这可能吗?如果是你能举个例子吗?
提前致谢。
感谢 Eugene 和 Travis 的建议,我能够使用 类 类型解决这个问题。解决方法如下:
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]
这是用法:
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]
}
}
}
值得注意的是 abort
而不是 error
。中止 returns Nothing
而错误 returns Unit
。当宏没有返回任何内容时,后者很好。
在 scala 2.11 及更高版本中,现在要简单得多。我创建了一个小项目来完成它:https://github.com/samupra/CaseClassChecker
我有以下宏:
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)
}
}
它在不涉及泛型时有效,但在涉及以下情况时失败:
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]
}
}
}
编译错误是我的:
MacroSpec.CaseClassContainer.T must be a case class
我想知道实例化 CaseClassContainer 时 T 是什么。这可能吗?如果是你能举个例子吗?
提前致谢。
感谢 Eugene 和 Travis 的建议,我能够使用 类 类型解决这个问题。解决方法如下:
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]
这是用法:
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]
}
}
}
值得注意的是 abort
而不是 error
。中止 returns Nothing
而错误 returns Unit
。当宏没有返回任何内容时,后者很好。
在 scala 2.11 及更高版本中,现在要简单得多。我创建了一个小项目来完成它:https://github.com/samupra/CaseClassChecker