1

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
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
bawejakunal
  • 1,678
  • 2
  • 25
  • 54

1 Answers1

4

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[_]
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Thanks, it does not seem to be working with singleton objects extending an abstract class. The compilation gets through without any failures. Sorry I should have described my question better initially. I believe this might not be working for singletons because of lazy evaluation of singleton objects. Any workarounds for that ? – bawejakunal Apr 12 '20 at 22:48
  • @bawejakunal How can lazy evaluation be relevant to compile time? Workarounds for what? Describe better what you try and what you get (compile errors etc.) – Dmytro Mitin Apr 12 '20 at 22:51
  • @bawejakunal I guess I already explained about `List` and `HList` in my answer. This can't work with `List`. Do you have further questions? – Dmytro Mitin Apr 12 '20 at 23:10
  • Thanks this works though I am wondering if this can work without using the kind-projector. For a project that I am working on adding that plugin may not be feasible in the short term. – bawejakunal Apr 13 '20 at 01:05
  • 1
    @bawejakunal Yes, surely this can be written without kind-projector with type lambda: `def noElementIsSubtypeOfT[L <: HList](l: L)(implicit liftAll: LiftAll[({ type λ[X] = X <:!< T })#λ, L]) = null`. – Dmytro Mitin Apr 13 '20 at 01:51