如何在Scala的编译时检查某些T是否为case类?

时间:2015-05-14 08:58:10

标签: scala scala-macros scala-2.11

我有以下宏:

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是什么。这甚至可能吗?如果是,你可以提供一个例子吗?

提前致谢。

2 个答案:

答案 0 :(得分:3)

感谢Eugene和Travis'建议我能用类型类来解决这个问题。这是解决方案:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox">click it

以下是用法:

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,而错误返回Nothing。当宏没有返回任何东西时,后者很好。

答案 1 :(得分:1)

在scala 2.11及更高版本中,它现在变得非常简单。我创建了一个小项目:https://github.com/samupra/CaseClassChecker