At compile time I want to verify that a class parameter is NOT an instance of a particular trait T. I know how to do it at runtime using require
or a case match
but wondering how this might be done at compile to prevent users from providing certain type of object mixins.
I've looked into scala macros/reflection but not able to wrap my head around that completely.
trait A
trait B
trait T
abstract class C extends A with B
case class P(c: C){
require(!c.isInstanceOf[T]) // how to do this at compile time ?
}
// usage as below
object c1 extends C
object c2 extends C
object c3 extends C
object c4 extends C with T
val l = List(c1, c2, c3, c4).map(k => P(k)) // should fail at compile time
You can't do this with List
. All elements in List(c1, c2, c3, c4)
will be of the same type i.e. C
and information that one of them has type C with T
will be lost.
new C {}
, new C with T {}
are runtime values of c1, c2, c3, c4
, compiler doesn't have access to them while compiling List(c1, c2, c3, c4)
.
You can do this with HList
. Using shapeless.<:!<
, shapeless.ops.hlist.LiftAll
and kind-projector
def noElementIsSubtypeOfT[L <: HList](l: L)(implicit liftAll: LiftAll[* <:!< T, L]) = null
noElementIsSubtypeOfT(c1 :: c2 :: c3 :: HNil) // compiles
// noElementIsSubtypeOfT(c1 :: c2 :: c3 :: c4 :: HNil) // doesn't compile
Or
def noElementIsSubtypeOfT[L <: HList : LiftAll[* <:!< T, *]](l: L) = null
For class parameter you can do
case class P[U <: C](c: U)(implicit ev: U <:!< T)
P(c1) // compiles
P(c2) // compiles
P(c3) // compiles
// P(c4) // doesn't compile
or
case class P[U <: C : * <:!< T](c: U)
Actually, there is a way to fix the code with List. You can make class P
implicit (so you define an implicit conversion) and specify the type of list List[P[_]](...)
(so called magnet pattern 1 2 3 4 5 6 7, P
is a magnet)
implicit class P[U <: C](c: U)(implicit ev: U <:!< T)
List[P[_]](c1, c2, c3) // compiles
List[P[_]](c1, c2, c3, c4) // doesn't compile, type mismatch: found: c4.type, required: P[_]