Черта, реализуемая классом, но не объектом - PullRequest
0 голосов
/ 24 июня 2019

Можно ли определить родительский класс или признак, который может быть реализован классом, но не объектом?

trait OnlyForClasses {
  // magic goes here
}

class Foo extends OnlyForClasses {
  // this is OK
}

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

Ответы [ 2 ]

3 голосов
/ 24 июня 2019

Попробуйте макроаннотацию для запечатанного признака

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

Как отладить макроаннотацию?


Или попробуйте материализованный класс типа

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 голос
/ 24 июня 2019

В качестве несовершенного решения вы можете добавить параметр конструктора, который пользователи не могут создать, и требовать его передачи в расширяющихся классах.Это также помешало бы им создавать экземпляры.Т.е. вы пишете (с небольшой хитростью, чтобы запретить 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)
...