Can I get a compile-time list of all of the case objects which derive from a sealed parent in Scala?

前端 未结 2 1033
醉梦人生
醉梦人生 2020-11-27 03:38

As has been discussed many times on SO, a Scala match will warn you if you don\'t exhaustively list all of the types deriving from a sealed class.

What I want is a

2条回答
  •  余生分开走
    2020-11-27 03:47

    Here's a working example using macros on 2.10.0-M6:

    (update: to make this example work in 2.10.0-M7, you need to replace c.TypeTag with c.AbsTypeTag; to make this example work in 2.10.0-RC1, c.AbsTypeTag needs to be replaced with c.WeakTypeTag)

    import scala.reflect.makro.Context
    
    object SealednessMacros {
      def exhaustive[P](ps: Seq[P]): Seq[P] = macro exhaustive_impl[P]
    
      def exhaustive_impl[P: c.TypeTag](c: Context)(ps: c.Expr[Seq[P]]) = {
        import c.universe._
    
        val symbol = typeOf[P].typeSymbol
    
        val seen = ps.tree match {
          case Apply(_, xs) => xs.map {
            case Select(_, name) => symbol.owner.typeSignature.member(name)
            case _ => throw new Exception("Can't check this expression!")
          }
          case _ => throw new Exception("Can't check this expression!")
        }
    
        val internal = symbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol]    
        if (!internal.isSealed) throw new Exception("This isn't a sealed type.")
    
        val descendants = internal.sealedDescendants.map(_.asInstanceOf[Symbol])
    
        val objs = (descendants - symbol).map(
          s => s.owner.typeSignature.member(s.name.toTermName)
        )
    
        if (seen.toSet == objs) ps else throw new Exception("Not exhaustive!")
      }
    }
    

    This obviously isn't very robust (for example, it assumes that you only have objects in the hierarchy, and it'll fail on A :: B :: C :: Nil), and it still requires some unpleasant casting, but it works as a quick proof-of-concept.

    First we compile this file with macros enabled:

    scalac -language:experimental.macros SealednessMacros.scala

    Now if we try to compile a file like this:

    object MyADT {
      sealed trait Parent
      case object A extends Parent
      case object B extends Parent
      case object C extends Parent
    }
    
    object Test extends App {
      import MyADT._
      import SealednessMacros._
    
      exhaustive[Parent](Seq(A, B, C))
      exhaustive[Parent](Seq(C, A, B))
      exhaustive[Parent](Seq(A, B))
    }
    

    We'll get a compile-time error on the Seq with the missing C:

    Test.scala:14: error: exception during macro expansion: 
    java.lang.Exception: Not exhaustive!
            at SealednessMacros$.exhaustive_impl(SealednessMacros.scala:29)
    
      exhaustive[Parent](Seq(A, B))
                        ^
    one error found
    

    Note that we need to help the compiler out with an explicit type parameter indicating the parent.

提交回复
热议问题