From b8687505e813073d4219facfcb5d164da17e8e4d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 9 Oct 2020 15:53:14 +0100 Subject: [PATCH] Implement -Xnon-strict-patmat-analysis --- .../tools/nsc/settings/ScalaSettings.scala | 1 + .../nsc/transform/patmat/MatchAnalysis.scala | 9 ++-- test/files/pos/t5365-nonStrict.scala | 42 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 test/files/pos/t5365-nonStrict.scala diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index cfb53ffba575..b96417bc8e4c 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -168,6 +168,7 @@ trait ScalaSettings extends StandardScalaSettings with Warnings { _: MutableSett def isAtLeastJunit = isTruthy || XmixinForceForwarders.value == "junit" } + val nonStrictPatmatAnalysis = BooleanSetting("-Xnon-strict-patmat-analysis", "Disable strict exhaustivity analysis, which assumes guards are false and refutable extractors don't match") val noUnsealedPatmatAnalysis = BooleanSetting("-Xno-unsealed-patmat-analysis", "Pattern match on an unsealed class without a catch-all.") // XML parsing options diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala index 35fb9226bcf4..916b14646412 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala @@ -521,6 +521,7 @@ trait MatchAnalysis extends MatchApproximation { // - back off (to avoid crying exhaustive too often) in unhandled cases val start = if (StatisticsStatics.areSomeColdStatsEnabled) statistics.startTimer(statistics.patmatAnaExhaust) else null var backoff = false + val strict = !settings.nonStrictPatmatAnalysis.value val approx = new TreeMakersToProps(prevBinder) val symbolicCases = approx.approximateMatch(cases, approx.onUnknown { tm => @@ -528,7 +529,7 @@ trait MatchAnalysis extends MatchApproximation { case BodyTreeMaker(_, _) => True // irrelevant -- will be discarded by symbolCase later case ExtractorTreeMaker(_, _, _) | ProductExtractorTreeMaker(_, _) - | GuardTreeMaker(_) => + | GuardTreeMaker(_) if strict => False case _ => debug.patmat("backing off due to "+ tm) @@ -767,6 +768,8 @@ trait MatchAnalysis extends MatchApproximation { // so, naively, you might try to construct a counter example like _ :: Nil(_ :: _, _ :: _), // since we didn't realize the tail of the outer cons was a Nil) def modelToCounterExample(scrutVar: Var)(varAssignment: Map[Var, (Seq[Const], Seq[Const])]): Option[CounterExample] = { + val strict = !settings.nonStrictPatmatAnalysis.value + // chop a path into a list of symbols def chop(path: Tree): List[Symbol] = path match { case Ident(_) => List(path.symbol) @@ -895,8 +898,8 @@ trait MatchAnalysis extends MatchApproximation { // if uniqueEqualTo contains more than one symbol of the same domain // then we can safely ignore these counter examples since we will eventually encounter // both counter examples separately - case _ if inSameDomain => - Some(WildcardExample) + // ... in strict mode, consider variable assignment as a wild counter-example + case _ if inSameDomain => if (strict) Some(WildcardExample) else None // not a valid counter-example, possibly since we have a definite type but there was a field mismatch // TODO: improve reasoning -- in the mean time, a false negative is better than an annoying false positive diff --git a/test/files/pos/t5365-nonStrict.scala b/test/files/pos/t5365-nonStrict.scala new file mode 100644 index 000000000000..3934ea7b7f6a --- /dev/null +++ b/test/files/pos/t5365-nonStrict.scala @@ -0,0 +1,42 @@ +// scalac: -Werror -Xnon-strict-patmat-analysis +// +// copy of neg/t5365.scala, which under -Xnon-strict-patmat-analysis gives no warnings +class C { + def nonExhautiveIfWeAssumeGuardsTrueOrFalse(x: Option[Int]): Int = x match { + case Some(n) if n % 2 == 0 => n + } + + def nonExhautiveIfWeAssumeGuardsFalse(x: Option[Int]): Int = x match { + case Some(n) if n % 2 == 0 => n + case None => 0 + } + + def inverseGuards(x: Option[Int]): Int = x match { + case Some(n) if n > 0 => n + case Some(n) if n <= 0 => ??? + case None => 0 + } + + def extractor(x: Option[Int]) = x match { + case Some(Extractor(_)) => + } + def repeatedExtractor(x: Option[Int]) = x match { + case Some(RepeatedExtractor(_)) => + } + def extractorStrict(x: Option[Int]) = x match { + case Some(Extractor(_)) => + case None => + } + def repeatedExtractorStrict(x: Option[Int]) = x match { + case Some(RepeatedExtractor(_)) => + case None => + } +} + +object Extractor { + def unapply(a: Any): Option[Any] = None +} + +object RepeatedExtractor { + def unapplySeq(a: Any): Option[Seq[Any]] = None +}