Skip to content

GADT typechecking requires unification analogue (via ConstraintHandling) #4176

Closed
@Blaisorblade

Description

@Blaisorblade

In #4069 @smarter suggests GADT constraints might require unification:

We end up with GADT constraints foo >:> S and S >:> foo but we don't have a mechanism to unify GADT constraints, so we end up with a loop in subtyping checks. We could add a special case to handle simple unifications like this but it seems like to be completely general we really need to reuse all of ConstraintHandling to deal with GADT constraints too.

So here's an unification-based use of GADTs: length-indexed vectors, which indeed doesn't typecheck. I also initially hoped to use erased for TNatSum (which would maybe work in Agda), but that'd be a request for enhancement in fact (and not sure it's one we should grant).

I think this is still a relatively simple testcase, and I'm not sure it requires ConstraintHandling in the way @smarter meant, but I hope it is helpful. Before fixing this, I'd suggest a review the literature to find a comprehensive design (and I volunteer to take a look).
"Principal Type Inference for GADTs", from 2016, is a relatively recent reference (and pretty original) with a decent review of related work; I think "Complete and Decidable Type Inference for GADTs" and "OutsideIn(x) modular type inference with local assumptions" describe GHC's basic approach, while "GADTs Meet Their Match: Pattern-matching Warnings That Account for GADTs, Guards, and Laziness" describe GHC extensions for warnings.

object NatsVects {
  sealed trait TNat
  case class TZero() extends TNat
  case class TSucc[N <: TNat] extends TNat

  object TNatSum {
    sealed trait TSum[M, N, R]
    case class TSumZero[N]() extends TSum[TZero, N, N]
    case class TSumM[M <: TNat, N, R <: TNat](sum: TSum[M, N, R]) extends TSum[TSucc[M], N, TSucc[R]]
  }
  import TNatSum._

  implicit def tSumZero[N]: TSum[TZero, N, N] =
    TSumZero()
    //new TSum[TZero, N, N]() {}
  implicit def tSumM[M <: TNat, N, R <: TNat](implicit sum: TSum[M, N, R]): TSum[TSucc[M], N, TSucc[R]] =
    TSumM(sum)
    //new TSum[TSucc[M], N, TSucc[R]] {}

  sealed trait Vec[+T, N <: TNat]
  case object VNil extends Vec[Nothing, TZero] // fails but in refchecks
  case class VCons[T, N <: TNat](x: T, xs: Vec[T, N]) extends Vec[T, TSucc[N]]

  implicit class vecOps[T, M <: TNat]($this: Vec[T, M]) extends AnyVal {
    def append[N <: TNat, R <: TNat](that: Vec[T, N])(implicit tsum: TSum[M, N, R]): Vec[T, R] = {
      // tsum match {
      //   case _: TSumZero[N] => // Here N = R
      //     $this match {
      //       case VNil =>
      //         //that
      //         that.asInstanceOf[Vec[T, R]]
      //       case VCons(x, xs) =>
      //         ???
      //     }
      //   case TSumM(sum) =>
      //     ???
      // }
      $this match {
        case VNil => // M = TZero
          tsum match {
            case _: TSumZero[TZero] =>
              //that
              that.asInstanceOf[Vec[T, R]]
            case _: TSumM[_, _, _] => // Impossible, this forces M = TSucc[M1]
              ???
          }
        case vxs: VCons[T, m1] => // M = TSucc[m1], xs: Vec[T, m1]
          val x = vxs.x
          val xs = vxs.xs
        //case VCons(x, xs) => 
          tsum match {
            case _: TSumZero[TZero] => // impossible, since this forces M = TZero.
              ???
            // fails
            // case tsum1: TSumM[`m1`, n, r] => // M = TSucc[m1], R = TSucc[r]
            //   implicit val tsum2 = tsum1.sum
            //   val appended = xs append that
            //   VCons(x, appended) // Vec[T, TSucc[r]]
            //works
            case tsum1: TSumM[`m1`, N, r] => // M = TSucc[m1], R = TSucc[r]
              implicit val tsum2 = tsum1.sum
              // I should be able to return:
              //VCons(x, xs append that) // Vec[T, TSucc[r]] = Vec[T, R]

              val vxs1 = xs append that
              val vxs2 = VCons(x, vxs1)
              // vxs1
              // [error] -- [E007] Type Mismatch Error: /Users/pgiarrusso/git/dotty-example-project/src/main/scala/playground/gadtVect.scala:65:14
              // [error] 65 |              vxs1
              // [error]    |              ^^^^
              // [error]    |    found:    NatsVects.Vec[T, r](vxs1)
              // [error]    |    required: NatsVects.Vec[T, R]
              // [error]    |
              // [error]    |    where:    r is a type in method append with bounds <: NatsVects.TNat
              // vxs2
              // [error] -- [E007] Type Mismatch Error: /Users/pgiarrusso/git/dotty-example-project/src/main/scala/playground/gadtVect.scala:71:14
              // [error] 71 |              vxs2
              // [error]    |              ^^^^
              // [error]    |    found:    NatsVects.VCons[T, r](vxs2)
              // [error]    |    required: NatsVects.Vec[T, R]
              // [error]    |
              // [error]    |    where:    r is a type in method append with bounds <: NatsVects.TNat

              vxs2.asInstanceOf // sorry this works
          }
      }
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions