【问题标题】:How to check if some T is a case class at compile time in Scala?如何在Scala的编译时检查某些T是否是案例类?
【发布时间】:2023-04-06 12:43:01
【问题描述】:

我有以下宏:

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

提前致谢。

【问题讨论】:

  • 不幸的是,您尝试做的事情不可能以这种方式实现。 Def 宏在您编写它们时立即在该位置展开,因此在展开点您将看到的只是 T。
  • 然而,如果你能改变你的方法来合并类型类,你可能会有所收获。即:你可以有一个CaseClass[T] 类型类,然后你就可以说class CaseClassContainer[T: CaseClass]。查看github.com/scalamacros/macrology201/tree/part2 了解如何编写此类宏。
  • 谢谢尤金!请随时将这些复制并粘贴到答案中,我会将其标记为正确。否是一个答案,也许比我 13 分钟前的情况要好得多。
  • 请注意,Twitter 的双射库提供了一个IsCaseClass 类型类,其中实例由a macro 生成。
  • 我认为特拉维斯的评论应该是一个答案:)

标签: scala scala-macros scala-2.11


【解决方案1】:

感谢 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。中止返回Nothing,而错误返回Unit。当宏没有返回任何内容时,后者很好。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-02
      • 1970-01-01
      • 1970-01-01
      • 2020-05-25
      • 1970-01-01
      • 1970-01-01
      • 2020-08-26
      • 1970-01-01
      相关资源
      最近更新 更多