Search code examples
scalagenericstypespattern-matchingshapeless

How can I verify type existence on compile time in Scala


I have the following traits and classes:

sealed trait Signal

sealed trait Description[T]

final case class S1(name: String) extends Signal

final case class D1(name: String) extends Description[S1]

What I try to achieve is that anyone who wants to add Signal will have (at compile time) to create a description.

I don't want to change the signature of Description but for sure not of Signal

I set my compiler to fail on warning, so I can leverage the fact that my ADT is sealed.

My idea was to have such a "compilation guard":

def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }

but I get the following error:

<console>:17: error: type mismatch;
 found   : D1
 required: Description[S]
       def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }
                                                                                              ^

Solution

  • def compilationGuard[S <: Signal](s: S): Description[S] = s match { case S1(name) => D1(name) }
    

    can't compile for the same reason as

    def returnItself[S <: Signal](s: S): S = s match { case S1(name) => S1(name) }
    

    Reasons are explained here in details:

    Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?

    Type mismatch on abstract type used in pattern matching

    If you don't want to mix Description logic to ADT or define instances of a type class like SignalMapper manually you can use Shapeless

    import shapeless.ops.coproduct.Mapper
    import shapeless.{:+:, CNil, Coproduct, Generic, Poly1}
    
    def compilationGuard[C <: Coproduct]()(implicit
      gen: Generic.Aux[Signal, C],
      mapper: Mapper[uniqueDescriptionPoly.type, C]
    ) = null
    
    object uniqueDescriptionPoly extends Poly1 {
      implicit def cse[S <: Signal, C1 <: Coproduct](implicit
        gen1: Generic.Aux[Description[S], C1],
        ev: C1 <:< (_ :+: CNil)
      ): Case.Aux[S, Null] = null
    }
    
    compilationGuard()
    

    Testing:

    final case class S1(name: String) extends Signal
    final case class S2(name: String) extends Signal
    final case class D1(name: String) extends Description[S1] 
    // doesn't compile
    
    final case class S1(name: String) extends Signal
    final case class S2(name: String) extends Signal
    final case class D1(name: String) extends Description[S1]
    final case class D2(name: String) extends Description[S1]
    // doesn't compile
    
    final case class S1(name: String) extends Signal
    final case class S2(name: String) extends Signal
    final case class D1(name: String) extends Description[S1]
    final case class D2(name: String) extends Description[S2]
    // compiles