scalashapelessscala-macrosscala-reflect

Scala: verify class parameter is not instanceOf a trait at compile time


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

Solution

  • 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[_]