Skip to content

Commit c9b3e5e

Browse files
committed
initial work on excluding GADT exhaustivity check
1 parent e675e6b commit c9b3e5e

File tree

5 files changed

+64
-7
lines changed

5 files changed

+64
-7
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,42 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
708708
else text
709709
}
710710

711+
/** Whether the counterexample is satisfiable */
712+
def satisfiable(sp: Space): Boolean = {
713+
def genConstraint(space: Space): List[(Type, Type)] = space match {
714+
case Prod(tp, unappTp, unappSym, ss, _) =>
715+
val tps = signature(unappTp, unappSym, ss.length)
716+
ss.zip(tps).flatMap {
717+
case (sp: Prod, _) => genConstraint(sp)
718+
case (Typ(tp1, _), tp2) => tp1 -> tp2 :: Nil
719+
// case _ => ??? // impossible
720+
}
721+
case Typ(_, _) => Nil
722+
// case _ => ??? // impossible
723+
}
724+
725+
def checkConstraint(constrs: List[(Type, Type)]): Boolean = {
726+
implicit val ctx1 = ctx.fresh.setNewTyperState()
727+
val tvarMap = collection.mutable.Map.empty[Symbol, TypeVar]
728+
val typeParamMap = new TypeMap() {
729+
override def apply(tp: Type): Type = tp match {
730+
case tref: TypeRef if tref.symbol.is(TypeParam) =>
731+
if (tvarMap.contains(tref.symbol)) tvarMap(tref.symbol)
732+
else {
733+
val tvar = newTypeVar(tref.underlying.bounds)
734+
tvarMap(tref.symbol) = tvar
735+
tvar
736+
}
737+
case tp => mapOver(tp)
738+
}
739+
}
740+
741+
constrs.foldLeft(true) { case (acc, (tp1, tp2)) => acc && typeParamMap(tp1) <:< typeParamMap(tp2) }
742+
}
743+
744+
checkConstraint(genConstraint(sp))
745+
}
746+
711747
/** Display spaces */
712748
def show(s: Space): String = {
713749
def params(tp: Type): List[Type] = tp.classSymbol.primaryConstructor.info.firstParamTypes
@@ -775,6 +811,15 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
775811
res
776812
}
777813

814+
def checkGADT(tp: Type): Boolean = {
815+
new TypeAccumulator[Boolean] {
816+
override def apply(b: Boolean, tp: Type): Boolean = tp match {
817+
case tref: TypeRef if tref.symbol.is(TypeParam) => true
818+
case tp => b || foldOver(b, tp)
819+
}
820+
}.apply(false, tp)
821+
}
822+
778823
def checkExhaustivity(_match: Match): Unit = {
779824
val Match(sel, cases) = _match
780825
val selTyp = sel.tpe.widen.dealias
@@ -785,10 +830,15 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
785830
debug.println(s"${x.pat.show} ====> ${show(space)}")
786831
space
787832
}).reduce((a, b) => Or(List(a, b)))
788-
val uncovered = simplify(minus(Typ(selTyp, true), patternSpace), aggressive = true)
789833

790-
if (uncovered != Empty)
791-
ctx.warning(PatternMatchExhaustivity(show(uncovered)), sel.pos)
834+
val checkGADTSAT = checkGADT(selTyp)
835+
836+
val uncovered =
837+
flatten(simplify(minus(Typ(selTyp, true), patternSpace), aggressive = true))
838+
.filter(s => s != Empty && (!checkGADTSAT || satisfiable(s)))
839+
840+
if (uncovered.nonEmpty)
841+
ctx.warning(PatternMatchExhaustivity(show(Or(uncovered))), sel.pos)
792842
}
793843

794844
def checkRedundancy(_match: Match): Unit = {

tests/patmat/3454.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object O {
2+
sealed trait Expr[T]
3+
case class BExpr(bool: Boolean) extends Expr[Boolean]
4+
case class IExpr(int: Int) extends Expr[Int]
5+
6+
def join[T](e1: Expr[T], e2: Expr[T]): Expr[T] = (e1, e2) match {
7+
case (IExpr(i1), IExpr(i2)) => IExpr(i1 + i2)
8+
case (BExpr(b1), BExpr(b2)) => BExpr(b1 & b2)
9+
}
10+
}

tests/patmat/exhausting.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
32: Pattern Match Exhaustivity: List(_, _: _*)
44
39: Pattern Match Exhaustivity: Bar3
55
44: Pattern Match Exhaustivity: (Bar2, Bar2)
6-
53: Pattern Match Exhaustivity: (Bar2, Bar2), (Bar2, Bar1), (Bar1, Bar3), (Bar1, Bar2)
6+
50: Pattern Match Exhaustivity: (Bar2, Bar2)

tests/patmat/exhausting.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ object Test {
4646
case (Bar2, Bar3) => ()
4747
case (Bar3, _) => ()
4848
}
49-
// fails for: (Bar1, Bar2)
50-
// fails for: (Bar1, Bar3)
51-
// fails for: (Bar2, Bar1)
5249
// fails for: (Bar2, Bar2)
5350
def fail5[T](xx: (Foo[T], Foo[T])) = xx match {
5451
case (Bar1, Bar1) => ()
File renamed without changes.

0 commit comments

Comments
 (0)