@@ -9,7 +9,6 @@ package scala.tools.nsc.transform.patmat
9
9
import scala .language .postfixOps
10
10
import scala .collection .mutable
11
11
import scala .reflect .internal .util .Statistics
12
- import scala .reflect .internal .util .Position
13
12
14
13
trait TreeAndTypeAnalysis extends Debugging {
15
14
import global ._
@@ -400,6 +399,7 @@ trait MatchAnalysis extends MatchApproximation {
400
399
trait MatchAnalyzer extends MatchApproximator {
401
400
def uncheckedWarning (pos : Position , msg : String ) = currentRun.reporting.uncheckedWarning(pos, msg)
402
401
def warn (pos : Position , ex : AnalysisBudget .Exception , kind : String ) = uncheckedWarning(pos, s " Cannot check match for $kind. \n ${ex.advice}" )
402
+ def reportWarning (message : String ) = global.reporter.warning(typer.context.tree.pos, message)
403
403
404
404
// TODO: model dependencies between variables: if V1 corresponds to (x: List[_]) and V2 is (x.hd), V2 cannot be assigned when V1 = null or V1 = Nil
405
405
// right now hackily implement this by pruning counter-examples
@@ -524,9 +524,11 @@ trait MatchAnalysis extends MatchApproximation {
524
524
val matchFailModels = findAllModelsFor(propToSolvable(matchFails))
525
525
526
526
val scrutVar = Var (prevBinderTree)
527
- val counterExamples = matchFailModels.map(modelToCounterExample(scrutVar))
528
-
529
- val pruned = CounterExample .prune(counterExamples).map(_.toString).sorted
527
+ val counterExamples = matchFailModels.flatMap(modelToCounterExample(scrutVar))
528
+ // sorting before pruning is important here in order to
529
+ // keep neg/t7020.scala stable
530
+ // since e.g. List(_, _) would cover List(1, _)
531
+ val pruned = CounterExample .prune(counterExamples.sortBy(_.toString)).map(_.toString)
530
532
531
533
if (Statistics .canEnable) Statistics .stopTimer(patmatAnaExhaust, start)
532
534
pruned
@@ -616,7 +618,7 @@ trait MatchAnalysis extends MatchApproximation {
616
618
// (the variables don't take into account type information derived from other variables,
617
619
// so, naively, you might try to construct a counter example like _ :: Nil(_ :: _, _ :: _),
618
620
// since we didn't realize the tail of the outer cons was a Nil)
619
- def modelToCounterExample (scrutVar : Var )(model : Model ): CounterExample = {
621
+ def modelToCounterExample (scrutVar : Var )(model : Model ): Option [ CounterExample ] = {
620
622
// x1 = ...
621
623
// x1.hd = ...
622
624
// x1.tl = ...
@@ -674,6 +676,7 @@ trait MatchAnalysis extends MatchApproximation {
674
676
private val fields : mutable.Map [Symbol , VariableAssignment ] = mutable.HashMap .empty
675
677
// need to prune since the model now incorporates all super types of a constant (needed for reachability)
676
678
private lazy val uniqueEqualTo = equalTo filterNot (subsumed => equalTo.exists(better => (better ne subsumed) && instanceOfTpImplies(better.tp, subsumed.tp)))
679
+ private lazy val inSameDomain = uniqueEqualTo forall (const => variable.domainSyms.exists(_.exists(_.const.tp =:= const.tp)))
677
680
private lazy val prunedEqualTo = uniqueEqualTo filterNot (subsumed => variable.staticTpCheckable <:< subsumed.tp)
678
681
private lazy val ctor = (prunedEqualTo match { case List (TypeConst (tp)) => tp case _ => variable.staticTpCheckable }).typeSymbol.primaryConstructor
679
682
private lazy val ctorParams = if (ctor.paramss.isEmpty) Nil else ctor.paramss.head
@@ -694,13 +697,13 @@ trait MatchAnalysis extends MatchApproximation {
694
697
// NoExample if the constructor call is ill-typed
695
698
// (thus statically impossible -- can we incorporate this into the formula?)
696
699
// beBrief is used to suppress negative information nested in tuples -- it tends to get too noisy
697
- def toCounterExample (beBrief : Boolean = false ): CounterExample =
698
- if (! allFieldAssignmentsLegal) NoExample
700
+ def toCounterExample (beBrief : Boolean = false ): Option [ CounterExample ] =
701
+ if (! allFieldAssignmentsLegal) Some ( NoExample )
699
702
else {
700
703
debug.patmat(" describing " + ((variable, equalTo, notEqualTo, fields, cls, allFieldAssignmentsLegal)))
701
704
val res = prunedEqualTo match {
702
705
// a definite assignment to a value
703
- case List (eq : ValueConst ) if fields.isEmpty => ValueExample (eq)
706
+ case List (eq : ValueConst ) if fields.isEmpty => Some ( ValueExample (eq) )
704
707
705
708
// constructor call
706
709
// or we did not gather any information about equality but we have information about the fields
@@ -713,30 +716,50 @@ trait MatchAnalysis extends MatchApproximation {
713
716
// figure out the constructor arguments from the field assignment
714
717
val argLen = (caseFieldAccs.length min ctorParams.length)
715
718
716
- (0 until argLen).map(i => fields.get(caseFieldAccs(i)).map(_.toCounterExample(brevity)) getOrElse WildcardExample ).toList
719
+ val examples = (0 until argLen).map(i => fields.get(caseFieldAccs(i)).map(_.toCounterExample(brevity)) getOrElse Some (WildcardExample )).toList
720
+ sequence(examples)
717
721
}
718
722
719
723
cls match {
720
- case ConsClass => ListExample (args())
721
- case _ if isTupleSymbol(cls) => TupleExample (args(brevity = true ))
722
- case _ => ConstructorExample (cls, args())
724
+ case ConsClass =>
725
+ args().map {
726
+ case List (NoExample , l : ListExample ) =>
727
+ // special case for neg/t7020.scala:
728
+ // if we find a counter example `??::*` we report `*::*` instead
729
+ // since the `??` originates from uniqueEqualTo containing several instanced of the same type
730
+ List (WildcardExample , l)
731
+ case args => args
732
+ }.map(ListExample )
733
+ case _ if isTupleSymbol(cls) => args(brevity = true ).map(TupleExample )
734
+ case _ if cls.isSealed && cls.isAbstractClass =>
735
+ // don't report sealed abstract classes, since
736
+ // 1) they can't be instantiated
737
+ // 2) we are already reporting any missing subclass (since we know the full domain)
738
+ // (see patmatexhaust.scala)
739
+ None
740
+ case _ => args().map(ConstructorExample (cls, _))
723
741
}
724
742
725
743
// a definite assignment to a type
726
- case List (eq) if fields.isEmpty => TypeExample (eq)
744
+ case List (eq) if fields.isEmpty => Some ( TypeExample (eq) )
727
745
728
746
// negative information
729
747
case Nil if nonTrivialNonEqualTo.nonEmpty =>
730
748
// negation tends to get pretty verbose
731
- if (beBrief) WildcardExample
749
+ if (beBrief) Some ( WildcardExample )
732
750
else {
733
751
val eqTo = equalTo.headOption getOrElse TypeConst (variable.staticTpCheckable)
734
- NegativeExample (eqTo, nonTrivialNonEqualTo)
752
+ Some ( NegativeExample (eqTo, nonTrivialNonEqualTo) )
735
753
}
736
754
755
+ // if uniqueEqualTo contains more than one symbol of the same domain
756
+ // then we can safely ignore these counter examples since we will eventually encounter
757
+ // both counter examples separately
758
+ case _ if inSameDomain => None
759
+
737
760
// not a valid counter-example, possibly since we have a definite type but there was a field mismatch
738
761
// TODO: improve reasoning -- in the mean time, a false negative is better than an annoying false positive
739
- case _ => NoExample
762
+ case _ => Some ( NoExample )
740
763
}
741
764
debug.patmatResult(" described as" )(res)
742
765
}
0 commit comments