diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index e3ad8f946000..af02bfed8411 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -804,6 +804,8 @@ class SpaceEngine(using Context) extends SpaceLogic { } private def exhaustivityCheckable(sel: Tree): Boolean = { + val seen = collection.mutable.Set.empty[Type] + // Possible to check everything, but be compatible with scalac by default def isCheckable(tp: Type): Boolean = val tpw = tp.widen.dealias @@ -815,16 +817,15 @@ class SpaceEngine(using Context) extends SpaceLogic { isCheckable(and.tp1) || isCheckable(and.tp2) }) || tpw.isRef(defn.BooleanClass) || - classSym.isAllOf(JavaEnumTrait) + classSym.isAllOf(JavaEnumTrait) || + classSym.is(Case) && { + if seen.add(tpw) then productSelectorTypes(tpw, sel.srcPos).exists(isCheckable(_)) + else true // recursive case class: return true and other members can still fail the check + } val res = !sel.tpe.hasAnnotation(defn.UncheckedAnnot) && { ctx.settings.YcheckAllPatmat.value || isCheckable(sel.tpe) - || { - val tpw = sel.tpe.widen.dealias - val classSym = tpw.classSymbol - classSym.is(Case) && productSelectorTypes(tpw, sel.srcPos).exists(isCheckable(_)) - } } debug.println(s"exhaustivity checkable: ${sel.show} = $res") diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 58e1133180ec..a0c46c829d0d 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -15,21 +15,26 @@ import java.nio.file.{Path => JPath} import scala.io.Source._ import org.junit.Test +class PatmatDefaultExhaustivityTest extends PatmatExhaustivityTest { + override val testsDir = "tests/patmat-default" + override val options = super.options.filter(_ != "-Ycheck-all-patmat") +} + class PatmatExhaustivityTest { val testsDir = "tests/patmat" // stop-after: patmatexhaust-huge.scala crash compiler - val options = List("-pagewidth", "80", "-color:never", "-Ystop-after:explicitSelf", "-Ycheck-all-patmat", "-classpath", TestConfiguration.basicClasspath) + def options = List("-pagewidth", "80", "-color:never", "-Ystop-after:explicitSelf", "-Ycheck-all-patmat", "-classpath", TestConfiguration.basicClasspath) private def compile(files: Seq[String]): Seq[String] = { val stringBuffer = new StringWriter() - val reporter = TestReporter.simplifiedReporter(new PrintWriter(stringBuffer)) + val printWriter = new PrintWriter(stringBuffer) + val reporter = TestReporter.simplifiedReporter(printWriter) try { Main.process((options ++ files).toArray, reporter, null) } catch { case e: Throwable => - println(s"Compile $files exception:") - e.printStackTrace() + e.printStackTrace(printWriter) } stringBuffer.toString.trim.replaceAll("\\s+\n", "\n") match { diff --git a/tests/patmat/i12485.scala b/tests/patmat-default/i12485.scala similarity index 100% rename from tests/patmat/i12485.scala rename to tests/patmat-default/i12485.scala diff --git a/tests/patmat-default/i13003.check b/tests/patmat-default/i13003.check new file mode 100644 index 000000000000..19fb6a466549 --- /dev/null +++ b/tests/patmat-default/i13003.check @@ -0,0 +1,4 @@ +4: Pattern Match Exhaustivity: One(Two(None)) +7: Pattern Match Exhaustivity: Two(None) +10: Pattern Match Exhaustivity: None, Some(None) +13: Pattern Match Exhaustivity: None, Some(None), Some(Some(None)) diff --git a/tests/patmat-default/i13003.scala b/tests/patmat-default/i13003.scala new file mode 100644 index 000000000000..af9158e09c5e --- /dev/null +++ b/tests/patmat-default/i13003.scala @@ -0,0 +1,14 @@ +case class One(two: Two) +case class Two(o: Option[Int]) + +def matchOneTwo(one: One) = one match + case One(Two(Some(i))) => "match!" + +def matchTwo(two: Two) = two match + case Two(Some(i)) => "match!" + +def matchOO(oo: Option[Option[Int]]) = oo match + case Some(Some(i)) => "match!" + +def matchOOO(ooo: Option[Option[Option[Int]]]) = ooo match + case Some(Some(Some(i))) => "match!"