6
6
7
7
package scala .tools .nsc .transform .patmat
8
8
9
+ import scala .annotation .tailrec
10
+ import scala .collection .immutable .{IndexedSeq , Iterable }
9
11
import scala .language .postfixOps
10
12
import scala .collection .mutable
11
13
import scala .reflect .internal .util .Statistics
@@ -514,8 +516,16 @@ trait MatchAnalysis extends MatchApproximation {
514
516
515
517
// find the models (under which the match fails)
516
518
val matchFailModels = findAllModelsFor(propToSolvable(matchFails))
519
+
517
520
val scrutVar = Var (prevBinderTree)
518
- val counterExamples = matchFailModels.flatMap(modelToCounterExample(scrutVar))
521
+ val counterExamples = {
522
+ matchFailModels.flatMap {
523
+ model =>
524
+ val varAssignments = expandModel(model)
525
+ varAssignments.flatMap(modelToCounterExample(scrutVar) _)
526
+ }
527
+ }
528
+
519
529
// sorting before pruning is important here in order to
520
530
// keep neg/t7020.scala stable
521
531
// since e.g. List(_, _) would cover List(1, _)
@@ -587,6 +597,8 @@ trait MatchAnalysis extends MatchApproximation {
587
597
case object WildcardExample extends CounterExample { override def toString = " _" }
588
598
case object NoExample extends CounterExample { override def toString = " ??" }
589
599
600
+ // returns a mapping from variable to
601
+ // equal and notEqual symbols
590
602
def modelToVarAssignment (model : Model ): Map [Var , (Seq [Const ], Seq [Const ])] =
591
603
model.toSeq.groupBy{f => f match {case (sym, value) => sym.variable} }.mapValues{ xs =>
592
604
val (trues, falses) = xs.partition(_._2)
@@ -600,20 +612,110 @@ trait MatchAnalysis extends MatchApproximation {
600
612
v + " (=" + v.path + " : " + v.staticTpCheckable + " ) " + assignment
601
613
}.mkString(" \n " )
602
614
603
- // return constructor call when the model is a true counter example
604
- // (the variables don't take into account type information derived from other variables,
605
- // so, naively, you might try to construct a counter example like _ :: Nil(_ :: _, _ :: _),
606
- // since we didn't realize the tail of the outer cons was a Nil)
607
- def modelToCounterExample (scrutVar : Var )(model : Model ): Option [CounterExample ] = {
615
+ /**
616
+ * The models we get from the DPLL solver need to be mapped back to counter examples.
617
+ * However there's no precalculated mapping model -> counter example. Even worse,
618
+ * not every valid model corresponds to a valid counter example.
619
+ * The reason is that restricting the valid models further would for example require
620
+ * a quadratic number of additional clauses. So to keep the optimistic case fast
621
+ * (i.e., all cases are covered in a pattern match), the infeasible counter examples
622
+ * are filtered later.
623
+ *
624
+ * The DPLL procedure keeps the literals that do not contribute to the solution
625
+ * unassigned, e.g., for `(a \/ b)`
626
+ * only {a = true} or {b = true} is required and the other variable can have any value.
627
+ *
628
+ * This function does a smart expansion of the model and avoids models that
629
+ * have conflicting mappings.
630
+ *
631
+ * For example for in case of the given set of symbols (taken from `t7020.scala`):
632
+ * "V2=2#16"
633
+ * "V2=6#19"
634
+ * "V2=5#18"
635
+ * "V2=4#17"
636
+ * "V2=7#20"
637
+ *
638
+ * One possibility would be to group the symbols by domain but
639
+ * this would only work for equality tests and would not be compatible
640
+ * with type tests.
641
+ * Another observation leads to a much simpler algorithm:
642
+ * Only one of these symbols can be set to true,
643
+ * since `V2` can at most be equal to one of {2,6,5,4,7}.
644
+ */
645
+ def expandModel (solution : Solution ): List [Map [Var , (Seq [Const ], Seq [Const ])]] = {
646
+
647
+ val model = solution.model
648
+
608
649
// x1 = ...
609
650
// x1.hd = ...
610
651
// x1.tl = ...
611
652
// x1.hd.hd = ...
612
653
// ...
613
654
val varAssignment = modelToVarAssignment(model)
655
+ debug.patmat(" var assignment for model " + model + " :\n " + varAssignmentString(varAssignment))
656
+
657
+ // group symbols that assign values to the same variables (i.e., symbols are mutually exclusive)
658
+ // (thus the groups are sets of disjoint assignments to variables)
659
+ val groupedByVar : Map [Var , List [Sym ]] = solution.unassigned.groupBy(_.variable)
660
+
661
+ val expanded = for {
662
+ (variable, syms) <- groupedByVar.toList
663
+ } yield {
664
+
665
+ val (equal, notEqual) = varAssignment.getOrElse(variable, Nil -> Nil )
666
+
667
+ def addVarAssignment (equalTo : List [Const ], notEqualTo : List [Const ]) = {
668
+ Map (variable -> (equal ++ equalTo, notEqual ++ notEqualTo))
669
+ }
670
+
671
+ // this assignment is needed in case that
672
+ // there exists already an assign
673
+ val allNotEqual = addVarAssignment(Nil , syms.map(_.const))
614
674
615
- debug.patmat(" var assignment for model " + model + " :\n " + varAssignmentString(varAssignment))
675
+ // this assignment is conflicting on purpose:
676
+ // a list counter example could contain wildcards: e.g. `List(_,_)`
677
+ val allEqual = addVarAssignment(syms.map(_.const), Nil )
616
678
679
+ if (equal.isEmpty) {
680
+ val oneHot = for {
681
+ s <- syms
682
+ } yield {
683
+ addVarAssignment(List (s.const), syms.filterNot(_ == s).map(_.const))
684
+ }
685
+ allEqual :: allNotEqual :: oneHot
686
+ } else {
687
+ allEqual :: allNotEqual :: Nil
688
+ }
689
+ }
690
+
691
+ if (expanded.isEmpty) {
692
+ List (varAssignment)
693
+ } else {
694
+ // we need the cartesian product here,
695
+ // since we want to report all missing cases
696
+ // (i.e., combinations)
697
+ val cartesianProd = expanded.reduceLeft((xs, ys) =>
698
+ for {map1 <- xs
699
+ map2 <- ys} yield {
700
+ map1 ++ map2
701
+ })
702
+
703
+ // add expanded variables
704
+ // note that we can just use `++`
705
+ // since the Maps have disjoint keySets
706
+ for {
707
+ m <- cartesianProd
708
+ } yield {
709
+ varAssignment ++ m
710
+ }
711
+ }
712
+ }
713
+
714
+ // return constructor call when the model is a true counter example
715
+ // (the variables don't take into account type information derived from other variables,
716
+ // so, naively, you might try to construct a counter example like _ :: Nil(_ :: _, _ :: _),
717
+ // since we didn't realize the tail of the outer cons was a Nil)
718
+ def modelToCounterExample (scrutVar : Var )(varAssignment : Map [Var , (Seq [Const ], Seq [Const ])]): Option [CounterExample ] = {
617
719
// chop a path into a list of symbols
618
720
def chop (path : Tree ): List [Symbol ] = path match {
619
721
case Ident (_) => List (path.symbol)
@@ -742,7 +844,7 @@ trait MatchAnalysis extends MatchApproximation {
742
844
// then we can safely ignore these counter examples since we will eventually encounter
743
845
// both counter examples separately
744
846
case _ if inSameDomain => None
745
-
847
+
746
848
// not a valid counter-example, possibly since we have a definite type but there was a field mismatch
747
849
// TODO: improve reasoning -- in the mean time, a false negative is better than an annoying false positive
748
850
case _ => Some (NoExample )
0 commit comments