Union types and generalisations

This comes down to being able to eliminate types from a union type, which I don’t think is directly possible. The same problem exists in Scala 2 for intersection types (A with B). I’ve had the need before to be able to eliminate types from an intersection type (in Scala 2) and solved it with a typeclass plus a fundep materialization macro. Here’s something similar in Scala 3 (lots of nice warnings, mainly because I had to do some backflips to construct the final union type as there doesn’t seem to be a way to construct types directly in the Scala 3 macro API yet):


trait Eliminate[A, B] {
  type Out
}

object Eliminate {

  import scala.quoted.{_, given}

  inline given derive[A, B] <: Eliminate[A, B] = ${ deriveImpl('[A], '[B]) }
  def deriveImpl[A, B](A: Type[A], B: Type[B])(given ctx: QuoteContext): Expr[Eliminate[A, B]] = {
    import ctx.tasty.{_, given}
    def expandOrs(typ: ctx.tasty.Type): List[ctx.tasty.Type] = typ match {
      case OrType(t1, t2) => expandOrs(t1) ++ expandOrs(t2)
      case typ => List(typ)
    }

    def orAll(typs: List[ctx.tasty.Type]): ctx.tasty.Type = {
      type X1
      type X2 
      typs match {
        case last :: Nil => last
        case first :: rest =>
          first.seal match {
            case first: scala.quoted.Type[X1] => orAll(rest).seal match {
              case rest: scala.quoted.Type[X2] => or[X1, X2](given first, rest, ctx).unseal.tpe
            }
          }
      }
    }

    val as = expandOrs(A.unseal.tpe)
    val b = B.unseal.tpe
    val withoutB = as.filterNot {
      typ => typ =:= b
    }
    val resultOrType = orAll(withoutB).seal
    '{ null: Eliminate[$A, $B] { type Out = $resultOrType }}
  }
  
  def or[X1, X2](given x1: Type[X1], x2: Type[X2], ctx: QuoteContext): Type[X1 | X2] = '[$x1 | $x2]

}

You can see it work like so:

scala> summon[(String | Int | Boolean) Eliminate Int]
val res0:
  Eliminate[String | Int | Boolean, Int]{
    Out = String | Boolean
  } = null

(notice the Out type has the resulting union type).

Hopefully someone who knows dotty a little more deeply can point out the proper way to do these things (like constructing a union type from two quoted types or tasty types) :smile:

3 Likes