可由类而非对象实现的特性

时间:2019-06-24 14:53:55

标签: scala scala-macros

是否可以定义可以由类而不是对象实现的父类或特征?

trait OnlyForClasses {
  // magic goes here
}

class Foo extends OnlyForClasses {
  // this is OK
}

object Bar extends OnlyForClasses {
  // INVALID, ideally fails a type check
}

2 个答案:

答案 0 :(得分:3)

尝试使用macro annotation获取密封特征

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

@compileTimeOnly("enable macro paradise to expand macro annotations")
class checkNoObjectChild extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro checkNoObjectChildMacro.impl
}

object checkNoObjectChildMacro {
  def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._
    annottees match {
      case q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _
        if mods.hasFlag(Flag.SEALED) =>

        val checkRecursion = c.openMacros.count(check =>
          (c.macroApplication.toString == check.macroApplication.toString) &&
          (c.enclosingPosition.toString == check.enclosingPosition.toString)
        )

        if (checkRecursion > 2)
          q"..$annottees"
        else {
          val tpe = c.typecheck(tq"$tpname", mode = c.TYPEmode, silent = true)
          val objectChildren = tpe.symbol.asClass.knownDirectSubclasses.filter(_.isModuleClass)

          if (objectChildren.isEmpty)
            q"..$annottees"
          else
            c.abort(c.enclosingPosition, s"Trait $tpname has object children: $objectChildren")
        }

      case _ =>
        c.abort(c.enclosingPosition, s"Not a sealed trait: $annottees")
    }
  }
}

@checkNoObjectChild
sealed trait OnlyForClasses {
}

class Foo extends OnlyForClasses {
  // compiles
}

object Bar extends OnlyForClasses {
  // doesn't compile
}

https://stackoverflow.com/a/20466423/5249621

How to debug a macro annotation?


或者尝试materializing类型类

trait NoObjectChild[T]

object NoObjectChild {
  implicit def materialize[T]: NoObjectChild[T] = macro impl[T]

  def impl[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._
    val tpe = weakTypeOf[T]
    val cls = tpe.typeSymbol.asClass

    if (!(cls.isTrait && cls.isSealed))
      c.abort(c.enclosingPosition, s"$tpe is not a sealed trait")

    val objectChildren = cls.knownDirectSubclasses.filter(_.isModuleClass)
    if (objectChildren.isEmpty)
      q"new NoObjectChild[$tpe] {}"
    else
      c.abort(c.enclosingPosition, s"Trait $tpe has object children: $objectChildren")
  }
}

sealed trait OnlyForClasses {
}

object OnlyForClasses {
  implicitly[NoObjectChild[OnlyForClasses]]
}


class Foo extends OnlyForClasses {
  // compiles
}

object Bar extends OnlyForClasses {
  // doesn't compile
}

答案 1 :(得分:1)

作为一个不完善的解决方案,您可以添加一个用户无法创建的构造函数参数,并要求在扩展类中传递它。这也将阻止他们创建实例。即您写(有些欺骗性来禁止null):

class DummyArgument private[your_package] (val x: Int) extends AnyVal {}

class OnlyForClasses(da: DummyArgument)

用户可以写

class Foo(da: DummyArgument) extends OnlyForClasses(da) {
  // this is OK
}

但不能写任何

object Bar extends OnlyForClasses(something) {
  // this is OK
}

object Bar(da: DummyArgument) extends OnlyForClasses {
  // this is OK
}

val foo = new Foo(something)