Reputation: 1250
Given this code:
sealed trait Parent
case object GetOne extends Parent
case object GetTwo extends Parent
Is it possible in Scala to enforce those constraints:
Parent
can only be extended by case object
case object
of Parent
must have their names start by Get
.Is it possible?
Upvotes: 0
Views: 107
Reputation: 51703
Try macros (with Shapeless)
import shapeless.ops.{coproduct, hlist}
import shapeless.{Coproduct, HList, LabelledGeneric}
import shapeless.ops.union.{Keys, Values}
def check[A] = new PartiallyApplied[A]
class PartiallyApplied[A] {
def apply[C <: Coproduct, K <: HList, V <: Coproduct]()(implicit
labelledGeneric: LabelledGeneric.Aux[A, C],
keys: Keys.Aux[C, K],
values: Values.Aux[C, V],
allKeysStartWithGet: hlist.LiftAll[StartsWithGet, K],
allValuesAreObjects: coproduct.LiftAll[IsObject, V]
) = null
}
import shapeless.Witness
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait StartsWithGet[S]
object StartsWithGet {
implicit def mkStartsWithGet[S <: Symbol]: StartsWithGet[S] = macro impl[S]
def impl[S <: Symbol : c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typ = weakTypeOf[S]
val witness = c.inferImplicitValue(
c.typecheck(tq"_root_.shapeless.Witness.Aux[$typ]", mode = c.TYPEmode).tpe,
silent = false
)
val str = c.eval(c.Expr[Witness.Lt[scala.Symbol]](
c.untypecheck(witness.duplicate)
)).value.name
if (str.startsWith("Get"))
q"new StartsWithGet[$typ] {}"
else c.abort(c.enclosingPosition, s"$str doesn't start with Get")
}
}
trait IsObject[A]
object IsObject {
implicit def mkIsObject[A]: IsObject[A] = macro impl[A]
def impl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val typ = weakTypeOf[A]
if (typ.typeSymbol.isModuleClass)
q"new IsObject[$typ] {}"
else c.abort(c.enclosingPosition, s"$typ is not object")
}
}
sealed trait Parent
case object GetOne extends Parent
case object GetTwo extends Parent
check[Parent]() // compiles
sealed trait Parent
case object GetOne extends Parent
case object Two extends Parent
check[Parent]() // doesn't compile
sealed trait Parent
case object GetOne extends Parent
case class GetTwo() extends Parent
check[Parent]() // doesn't compile
Alternatively StartsWithGet
can be defined via https://github.com/fthomas/singleton-ops
import shapeless.tag.@@
import singleton.ops.{Require, StartsWith}
trait StartsWithGet[S]
object StartsWithGet {
implicit def mkStartsWithGet[S <: String](implicit
startsWith: Require[S StartsWith "Get"]
): StartsWithGet[Symbol @@ S] = null
}
Upvotes: 4
Reputation: 48430
Parent can only be extended by case object
In Scala 3 you could define an enumeration
enum Parent {
case GetOne, GetTwo
}
which forces the members to be effectively case objects.
Upvotes: 3
Reputation: 22895
Parent can only be extended by case object
You may get close using Singleton.
(as mentioned by @MateuszKubuszok)
Here is an example:
sealed trait Foo extends Product with Serializable { self: Singleton => }
Then this works:
final case object A extends Foo
final case object B extends Foo
And this doesn't:
final case object A extends Foo
final case class B(blah: String) extends Foo
The child case object of Parent must have their names start by Get.
Not using standard Scala.
Maybe with macros or something like that, but really feels like a strange requisite; do you plan to get those instances through reflection? or what is the reason to wanting that?
(in any case, seems like something that may be better handled by code reviews and maybe a scalafix rule)
Upvotes: 4