Skip to content

A simple way to check exhaustivity of GADTs #3510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 11, 2017

Conversation

liufengyun
Copy link
Contributor

This is an alternative to #3454.

The idea (implemented in OCaml, briefly mentioned in the space paper) is simple: just exclude counterexamples that are not satisfiable.

The PR passed all GADT tests, and it should have better performance by only enabling the checking for invariant GADTs. It doesn't do any changes to the core algorithm.

Many thanks to @AleksanderBG for his work on checking GADTs and pushing the limits of exhaustivity check for Scala.

cc/ @AleksanderBG: we're still interested in your algorithm , it would be nice to have detailed evaluation of the two approaches when #3454 is done.

val typeParamMap = new TypeMap() {
override def apply(tp: Type): Type = tp match {
case tref: TypeRef if tref.symbol.is(TypeParam) =>
if (tvarMap.contains(tref.symbol)) tvarMap(tref.symbol)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block looks like a tvarMap.getOrElseUpdate(tref.symbol, newTypeVar(tref.underlying.bounds))

ss.zip(tps).flatMap {
case (sp : Prod, tp) => sp.tp -> tp :: genConstraint(sp)
case (Typ(tp1, _), tp2) => tp1 -> tp2 :: Nil
case _ => ??? // impossible
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that we should use something other than ??? for unreachable cases, to me ??? really has the semantic of a TODO: "implementation is missing here, to be added later"

Maybe something like this? Not sure where should that definition could go...

def impossible: Nothing = throw new AssertionError("This expression shouldn't be reachable.")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert(false, "Unreachable code")?

Copy link
Contributor

@OlivierBlanvillain OlivierBlanvillain Nov 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@allanrenucci assert returns Unit

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad...

@@ -775,6 +810,15 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
res
}

def checkGADT(tp: Type): Boolean = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean by checkGADT the implementation looks like it's computing isDeeplyInvariant

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, something like shouldCheckGADT with a comment is more friendly.

@abgruszecki
Copy link
Contributor

@liufengyun Is it possible to use ctx.gadt to further restrict initial tvar bounds? There are cases like the following one:

object O {
  sealed trait NumExpr[T]
  case class IntLit(v: Int) extends NumExpr[Int]
  case class DoubleLit(v: Double) extends NumExpr[Double]
  case class Mul[T](n1: NumExpr[T], n2: NumExpr[T]) extends NumExpr[T]

  def simplify[T](e: NumExpr[T]): NumExpr[T] = e match {
    case Mul(IntLit(i), ie) => ie match {
      case IntLit(0) => IntLit(0)
      case IntLit(1) => IntLit(i)
      case IntLit(_) | Mul(_, _) => e
    }
    case _ => e
  }
}

where the typechecker already knows that T ~ Int from the outer match. If I understand correctly that these bounds are recorded in ctx.gadt, using it would improve the warnings while barely requiring any adjustments.

@liufengyun
Copy link
Contributor Author

liufengyun commented Nov 29, 2017

@AleksanderBG Good observation. The ctx.gadt information is only available during type checking (phase Frontend), in pattern matcher it's too late to get that information.

Edited: Also, I think it's up to the typer to give ie the type IntLit.

@liufengyun liufengyun merged commit 7e919b6 into scala:master Dec 11, 2017
@liufengyun liufengyun deleted the simple-gadt-check branch December 11, 2017 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants