From 2da30980763e324960c7c60affbdef71b63f2130 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 14 Aug 2023 18:08:12 +0100 Subject: [PATCH 1/7] Reuse & switch to UncheckedTypePattern [Cherry-picked ee6d386fd4883aa9ea7a5f5c096c6c324a0ba1ce] --- .../dotty/tools/dotc/reporting/messages.scala | 4 +-- .../tools/dotc/transform/TypeTestsCasts.scala | 2 +- tests/neg/15981.check | 4 ++- tests/neg/i12253.check | 8 ++++-- tests/neg/i16728.check | 4 ++- tests/neg/i4812.check | 28 ++++++++++++++----- tests/neg/nowarn.check | 4 ++- 7 files changed, 39 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 03a3d8e57438..007f0a56f8d8 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -895,9 +895,9 @@ extends Message(PatternMatchExhaustivityID) { } } -class UncheckedTypePattern(msgFn: => String)(using Context) +class UncheckedTypePattern(argType: Type, whyNot: String)(using Context) extends PatternMatchMsg(UncheckedTypePatternID) { - def msg(using Context) = msgFn + def msg(using Context) = i"the type test for $argType cannot be checked at runtime because $whyNot" def explain(using Context) = i"""|Type arguments and type refinements are erased during compile time, thus it's |impossible to check them at run-time. diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index f5cb8eab73a4..9eaf92a83b7a 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -363,7 +363,7 @@ object TypeTestsCasts { if !isTrusted && !isUnchecked then val whyNot = whyUncheckable(expr.tpe, argType, tree.span) if whyNot.nonEmpty then - report.uncheckedWarning(em"the type test for $argType cannot be checked at runtime because $whyNot", expr.srcPos) + report.uncheckedWarning(UncheckedTypePattern(argType, whyNot), expr.srcPos) transformTypeTest(expr, argType, flagUnrelated = enclosingInlineds.isEmpty) // if test comes from inlined code, dont't flag it even if it always false } diff --git a/tests/neg/15981.check b/tests/neg/15981.check index c4d677b486e9..10745839c566 100644 --- a/tests/neg/15981.check +++ b/tests/neg/15981.check @@ -1,4 +1,6 @@ --- Error: tests/neg/15981.scala:4:45 ----------------------------------------------------------------------------------- +-- [E092] Pattern Match Error: tests/neg/15981.scala:4:45 -------------------------------------------------------------- 4 | override def equals(any: Any): Boolean = any.isInstanceOf[PosInt] // error | ^^^ | the type test for PosInt cannot be checked at runtime because it's a local class + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i12253.check b/tests/neg/i12253.check index 74fa3db47b0f..75a698249dee 100644 --- a/tests/neg/i12253.check +++ b/tests/neg/i12253.check @@ -1,9 +1,13 @@ --- Error: tests/neg/i12253.scala:13:10 --------------------------------------------------------------------------------- +-- [E092] Pattern Match Error: tests/neg/i12253.scala:13:10 ------------------------------------------------------------ 13 | case extractors.InlinedLambda(_, Select(_, name)) => Expr(name) // error // error | ^ |the type test for extractors.q2.reflect.Term cannot be checked at runtime because it refers to an abstract type member or type parameter --- Error: tests/neg/i12253.scala:13:38 --------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Error: tests/neg/i12253.scala:13:38 ------------------------------------------------------------ 13 | case extractors.InlinedLambda(_, Select(_, name)) => Expr(name) // error // error | ^ |the type test for q1.reflect.Select cannot be checked at runtime because it refers to an abstract type member or type parameter + | + | longer explanation available when compiling with `-explain` there was 1 deprecation warning; re-run with -deprecation for details diff --git a/tests/neg/i16728.check b/tests/neg/i16728.check index 9bc8b7457ce2..93cc215696c2 100644 --- a/tests/neg/i16728.check +++ b/tests/neg/i16728.check @@ -1,4 +1,6 @@ --- Error: tests/neg/i16728.scala:18:11 --------------------------------------------------------------------------------- +-- [E092] Pattern Match Error: tests/neg/i16728.scala:18:11 ------------------------------------------------------------ 18 | case tx : C[Int]#X => // error | ^ | the type test for C[Int] cannot be checked at runtime because its type arguments can't be determined from A + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i4812.check b/tests/neg/i4812.check index 275cda56defe..f4aee0e35dde 100644 --- a/tests/neg/i4812.check +++ b/tests/neg/i4812.check @@ -1,28 +1,42 @@ --- Error: tests/neg/i4812.scala:8:11 ----------------------------------------------------------------------------------- +-- [E092] Pattern Match Error: tests/neg/i4812.scala:8:11 -------------------------------------------------------------- 8 | case prev: A => // error: the type test for A cannot be checked at runtime | ^ | the type test for A cannot be checked at runtime because it's a local class --- Error: tests/neg/i4812.scala:18:11 ---------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Error: tests/neg/i4812.scala:18:11 ------------------------------------------------------------- 18 | case prev: A => // error: the type test for A cannot be checked at runtime | ^ | the type test for A cannot be checked at runtime because it's a local class --- Error: tests/neg/i4812.scala:28:11 ---------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Error: tests/neg/i4812.scala:28:11 ------------------------------------------------------------- 28 | case prev: A => // error: the type test for A cannot be checked at runtime | ^ | the type test for A cannot be checked at runtime because it's a local class --- Error: tests/neg/i4812.scala:38:11 ---------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Error: tests/neg/i4812.scala:38:11 ------------------------------------------------------------- 38 | case prev: A => // error: the type test for A cannot be checked at runtime | ^ | the type test for A cannot be checked at runtime because it's a local class --- Error: tests/neg/i4812.scala:50:13 ---------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Error: tests/neg/i4812.scala:50:13 ------------------------------------------------------------- 50 | case prev: A => // error: the type test for A cannot be checked at runtime | ^ | the type test for A cannot be checked at runtime because it's a local class --- Error: tests/neg/i4812.scala:60:11 ---------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Error: tests/neg/i4812.scala:60:11 ------------------------------------------------------------- 60 | case prev: A => // error: the type test for A cannot be checked at runtime | ^ | the type test for A cannot be checked at runtime because it's a local class --- Error: tests/neg/i4812.scala:96:11 ---------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Error: tests/neg/i4812.scala:96:11 ------------------------------------------------------------- 96 | case x: B => // error: the type test for B cannot be checked at runtime | ^ | the type test for B cannot be checked at runtime because it's a local class + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/nowarn.check b/tests/neg/nowarn.check index 3e16314f643e..a8075335290a 100644 --- a/tests/neg/nowarn.check +++ b/tests/neg/nowarn.check @@ -66,10 +66,12 @@ Matching filters for @nowarn or -Wconf: 49 |def t7c = f // warning (deprecation) | ^ | method f is deprecated --- Unchecked Warning: tests/neg/nowarn.scala:55:7 ---------------------------------------------------------------------- +-- [E092] Pattern Match Unchecked Warning: tests/neg/nowarn.scala:55:7 ------------------------------------------------- 55 | case _: List[Int] => 0 // warning (patmat, unchecked) | ^ |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from Any + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg/nowarn.scala:33:1 ---------------------------------------------------------------------------------- 33 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) |^^^^^^^^^^^^^^^ From 92ba54a239aae74a25fbd99c02c02133f428591a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 14 Aug 2023 18:10:54 +0100 Subject: [PATCH 2/7] Vulpix: Extract common code & drop unused suppressErrors [Cherry-picked 845bb1c4c63b110513080c6b4872aca49b348795] --- .../dotty/tools/vulpix/ParallelTesting.scala | 91 +++++++++---------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index bccbcbee29e1..6880b2592b1b 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -226,14 +226,14 @@ trait ParallelTesting extends RunnerOrchestration { self => Try(testSource match { case testSource @ JointCompilationSource(name, files, flags, outDir, fromTasty, decompilation) => val reporter = - if (fromTasty) compileFromTasty(flags, suppressErrors, outDir) - else compile(testSource.sourceFiles, flags, suppressErrors, outDir) + if (fromTasty) compileFromTasty(flags, outDir) + else compile(testSource.sourceFiles, flags, outDir) List(reporter) case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => testSource.compilationGroups.map { (group, files) => if group.compiler.isEmpty then - compile(files, flags, suppressErrors, outDir) + compile(files, flags, outDir) else compileWithOtherCompiler(group.compiler, files, flags, outDir) } @@ -469,7 +469,7 @@ trait ParallelTesting extends RunnerOrchestration { self => registerCompletion() throw e - protected def compile(files0: Array[JFile], flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { + protected def compile(files0: Array[JFile], flags0: TestFlags, targetDir: JFile): TestReporter = { import scala.util.Properties.* def flattenFiles(f: JFile): Array[JFile] = @@ -634,7 +634,7 @@ trait ParallelTesting extends RunnerOrchestration { self => reporter - protected def compileFromTasty(flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { + protected def compileFromTasty(flags0: TestFlags, targetDir: JFile): TestReporter = { val tastyOutput = new JFile(targetDir.getPath + "_from-tasty") tastyOutput.mkdir() val flags = flags0 and ("-d", tastyOutput.getPath) and "-from-tasty" @@ -653,6 +653,12 @@ trait ParallelTesting extends RunnerOrchestration { self => private def mkLogLevel = if suppressErrors || suppressAllOutput then ERROR + 1 else ERROR private def mkReporter = TestReporter.reporter(realStdout, logLevel = mkLogLevel) + protected def diffCheckfile(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable) = + checkFile(testSource).foreach(diffTest(testSource, _, reporterOutputLines(reporters), reporters, logger)) + + private def reporterOutputLines(reporters: Seq[TestReporter]): List[String] = + reporters.flatMap(_.consoleOutput.split("\n")).toList + private[ParallelTesting] def executeTestSuite(): this.type = { assert(testSourcesCompleted == 0, "not allowed to re-use a `CompileRun`") if filteredSources.nonEmpty then @@ -808,10 +814,7 @@ trait ParallelTesting extends RunnerOrchestration { self => end maybeFailureMessage override def onSuccess(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable): Unit = - checkFile(testSource).foreach(diffTest(testSource, _, reporterOutputLines(reporters), reporters, logger)) - - def reporterOutputLines(reporters: Seq[TestReporter]): List[String] = - reporters.flatMap(_.consoleOutput.split("\n")).toList + diffCheckfile(testSource, reporters, logger) // In neg-tests we allow two or three types of error annotations. // Normally, `// error` must be annotated on the correct line number. @@ -1014,20 +1017,8 @@ trait ParallelTesting extends RunnerOrchestration { self => * compilation without generating errors and that they do not crash the * compiler */ - def checkCompile()(implicit summaryReport: SummaryReporting): this.type = { - val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() - - cleanup() - - if (!shouldFail && test.didFail) { - fail(s"Expected no errors when compiling, failed for the following reason(s):\n${reasonsForFailure(test)}\n") - } - else if (shouldFail && !test.didFail && test.skipCount == 0) { - fail("Pos test should have failed, but didn't") - } - - this - } + def checkCompile()(implicit summaryReport: SummaryReporting): this.type = + checkPass(new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput), "Pos") /** Creates a "neg" test run, which makes sure that each test generates the * correct number of errors at the correct positions. It also makes sure @@ -1047,35 +1038,16 @@ trait ParallelTesting extends RunnerOrchestration { self => end checkExpectedErrors /** Creates a "fuzzy" test run, which makes sure that each test compiles (or not) without crashing */ - def checkNoCrash()(implicit summaryReport: SummaryReporting): this.type = { - val test = new NoCrashTest(targets, times, threadLimit, shouldSuppressOutput).executeTestSuite() - - cleanup() - - if (test.didFail) { - fail("Fuzzy test shouldn't have crashed, but did") - } - - this - } + def checkNoCrash()(implicit summaryReport: SummaryReporting): this.type = + checkFail(new NoCrashTest(targets, times, threadLimit, shouldSuppressOutput), "Fuzzy") /** Creates a "run" test run, which is a superset of "pos". In addition to * making sure that all tests pass compilation and that they do not crash * the compiler; it also makes sure that all tests can run with the * expected output */ - def checkRuns()(implicit summaryReport: SummaryReporting): this.type = { - val test = new RunTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() - - cleanup() - - if !shouldFail && test.didFail then - fail(s"Run test failed, but should not, reasons:\n${ reasonsForFailure(test) }") - else if shouldFail && !test.didFail && test.skipCount == 0 then - fail("Run test should have failed, but did not") - - this - } + def checkRuns()(implicit summaryReport: SummaryReporting): this.type = + checkPass(new RunTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput), "Run") /** Tests `-rewrite`, which makes sure that the rewritten files still compile * and agree with the expected result (if specified). @@ -1100,15 +1072,34 @@ trait ParallelTesting extends RunnerOrchestration { self => target.copy(dir = copyToDir(outDir, dir)) } - val test = new RewriteTest(copiedTargets, checkFileMap, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + val test = new RewriteTest(copiedTargets, checkFileMap, times, threadLimit, shouldFail || shouldSuppressOutput) + + checkFail(test, "Rewrite") + } + + private def checkPass(test: Test, desc: String): this.type = + test.executeTestSuite() cleanup() - if test.didFail then - fail("Rewrite test failed") + if !shouldFail && test.didFail then + fail(s"$desc test failed, but should not, reasons:\n${reasonsForFailure(test)}") + else if shouldFail && !test.didFail && test.skipCount == 0 then + fail(s"$desc test should have failed, but didn't") + + this + + private def checkFail(test: Test, desc: String): this.type = + test.executeTestSuite() + + cleanup() + + if shouldFail && !test.didFail && test.skipCount == 0 then + fail(s"$desc test shouldn't have failed, but did. Reasons:\n${reasonsForFailure(test)}") + else if !shouldFail && test.didFail then + fail(s"$desc test failed") this - } /** Deletes output directories and files */ private def cleanup(): this.type = { From e576ac6592d748c36543a520aa2e19fe40c5334f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 14 Aug 2023 18:13:43 +0100 Subject: [PATCH 3/7] Vulpix: Introduce WarnTest/checkWarnings [Cherry-picked 4740017dc2e13ff87aa85dc0887d68469a232764] --- compiler/test/dotty/tools/dotc/CompilationTests.scala | 9 +++++++++ compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 74c5c0dbb1e1..01c369d40a5d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -108,6 +108,15 @@ class CompilationTests { ).times(2).checkCompile() } + // Warning tests ------------------------------------------------------------ + + @Test def warn: Unit = { + implicit val testGroup: TestGroup = TestGroup("compileWarn") + aggregateTests( + compileFilesInDir("tests/warn", defaultOptions), + ).checkWarnings() + } + // Negative tests ------------------------------------------------------------ @Test def negAll: Unit = { diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 6880b2592b1b..bcf17c37fa0b 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -723,6 +723,12 @@ trait ParallelTesting extends RunnerOrchestration { self => private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) extends Test(testSources, times, threadLimit, suppressAllOutput) + private final class WarnTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) + extends Test(testSources, times, threadLimit, suppressAllOutput): + override def suppressErrors = true + override def onSuccess(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable): Unit = + diffCheckfile(testSource, reporters, logger) + private final class RewriteTest(testSources: List[TestSource], checkFiles: Map[JFile, JFile], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) extends Test(testSources, times, threadLimit, suppressAllOutput) { private def verifyOutput(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable) = { @@ -1020,6 +1026,9 @@ trait ParallelTesting extends RunnerOrchestration { self => def checkCompile()(implicit summaryReport: SummaryReporting): this.type = checkPass(new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput), "Pos") + def checkWarnings()(implicit summaryReport: SummaryReporting): this.type = + checkPass(new WarnTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput), "Warn") + /** Creates a "neg" test run, which makes sure that each test generates the * correct number of errors at the correct positions. It also makes sure * that none of these tests crashes the compiler. From 333fa6c323bc38db9d0bc6bbb355feb92c25d3e0 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 9 Aug 2023 19:01:09 +0100 Subject: [PATCH 4/7] Unsuppress unchecked warnings And check it with a pos checkfile. [Cherry-picked 904583460a1649e248949b329ca7fdb3aa4afe01] --- .../dotty/tools/dotc/reporting/Message.scala | 2 +- .../dotty/tools/dotc/reporting/messages.scala | 1 + .../tools/dotc/transform/TypeTestsCasts.scala | 4 +- .../tools/dotc/transform/patmat/Space.scala | 16 +++--- tests/neg-deep-subtype/enum-approx2.scala | 12 ---- tests/pending/neg/i16451.check | 24 -------- tests/warn/enum-approx2.check | 20 +++++++ tests/warn/enum-approx2.scala | 10 ++++ tests/{neg-deep-subtype => warn}/i11178.scala | 2 - tests/warn/i16451.check | 56 +++++++++++++++++++ tests/{pending/neg => warn}/i16451.scala | 22 +++++--- tests/warn/i5826.check | 34 +++++++++++ tests/{neg-deep-subtype => warn}/i5826.scala | 6 +- tests/{neg-deep-subtype => warn}/i8932.scala | 2 - .../suppressed-type-test-warnings.scala | 2 - 15 files changed, 148 insertions(+), 65 deletions(-) delete mode 100644 tests/neg-deep-subtype/enum-approx2.scala delete mode 100644 tests/pending/neg/i16451.check create mode 100644 tests/warn/enum-approx2.check create mode 100644 tests/warn/enum-approx2.scala rename tests/{neg-deep-subtype => warn}/i11178.scala (94%) create mode 100644 tests/warn/i16451.check rename tests/{pending/neg => warn}/i16451.scala (60%) create mode 100644 tests/warn/i5826.check rename tests/{neg-deep-subtype => warn}/i5826.scala (88%) rename tests/{neg-deep-subtype => warn}/i8932.scala (88%) rename tests/{neg => warn}/suppressed-type-test-warnings.scala (95%) diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 1bfcf5c2f9dd..c971719a1712 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -377,7 +377,7 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self => override def canExplain = true /** Override with `true` for messages that should always be shown even if their - * position overlaps another messsage of a different class. On the other hand + * position overlaps another message of a different class. On the other hand * multiple messages of the same class with overlapping positions will lead * to only a single message of that class to be issued. */ diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 007f0a56f8d8..f43b19708258 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -897,6 +897,7 @@ extends Message(PatternMatchExhaustivityID) { class UncheckedTypePattern(argType: Type, whyNot: String)(using Context) extends PatternMatchMsg(UncheckedTypePatternID) { + override def showAlways = true def msg(using Context) = i"the type test for $argType cannot be checked at runtime because $whyNot" def explain(using Context) = i"""|Type arguments and type refinements are erased during compile time, thus it's diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 9eaf92a83b7a..f682b54ae731 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -74,7 +74,7 @@ object TypeTestsCasts { }.apply(tp) /** Returns true if the type arguments of `P` can be determined from `X` */ - def typeArgsTrivial(X: Type, P: AppliedType)(using Context) = inContext(ctx.fresh.setExploreTyperState().setFreshGADTBounds) { + def typeArgsDeterminable(X: Type, P: AppliedType)(using Context) = inContext(ctx.fresh.setExploreTyperState().setFreshGADTBounds) { val AppliedType(tycon, _) = P def underlyingLambda(tp: Type): TypeLambda = tp.ensureLambdaSub match { @@ -155,7 +155,7 @@ object TypeTestsCasts { case x => // always false test warnings are emitted elsewhere TypeComparer.provablyDisjoint(x, tpe.derivedAppliedType(tycon, targs.map(_ => WildcardType))) - || typeArgsTrivial(X, tpe) + || typeArgsDeterminable(X, tpe) ||| i"its type arguments can't be determined from $X" } case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 2464ca448763..90039422548e 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -394,7 +394,7 @@ object SpaceEngine { project(pat) case Typed(_, tpt) => - Typ(erase(tpt.tpe.stripAnnots, isValue = true), decomposed = false) + Typ(erase(tpt.tpe.stripAnnots, isValue = true, isTyped = true), decomposed = false) case This(_) => Typ(pat.tpe.stripAnnots, decomposed = false) @@ -462,24 +462,26 @@ object SpaceEngine { * If `isValue` is true, then pattern-bound symbols are erased to its upper bound. * This is needed to avoid spurious unreachable warnings. See tests/patmat/i6197.scala. */ - private def erase(tp: Type, inArray: Boolean = false, isValue: Boolean = false)(using Context): Type = - trace(i"erase($tp${if inArray then " inArray" else ""}${if isValue then " isValue" else ""})", debug)(tp match { + private def erase(tp: Type, inArray: Boolean = false, isValue: Boolean = false, isTyped: Boolean = false)(using Context): Type = + trace(i"erase($tp${if inArray then " inArray" else ""}${if isValue then " isValue" else ""}${if isTyped then " isTyped" else ""})", debug)(tp match { case tp @ AppliedType(tycon, args) if tycon.typeSymbol.isPatternBound => WildcardType case tp @ AppliedType(tycon, args) => val inArray = tycon.isRef(defn.ArrayClass) - val args2 = args.map(arg => erase(arg, inArray = inArray, isValue = false)) + val args2 = + if isTyped && !inArray then args.map(_ => WildcardType) + else args.map(arg => erase(arg, inArray = inArray, isValue = false)) tp.derivedAppliedType(erase(tycon, inArray, isValue = false), args2) case tp @ OrType(tp1, tp2) => - OrType(erase(tp1, inArray, isValue), erase(tp2, inArray, isValue), tp.isSoft) + OrType(erase(tp1, inArray, isValue, isTyped), erase(tp2, inArray, isValue, isTyped), tp.isSoft) case AndType(tp1, tp2) => - AndType(erase(tp1, inArray, isValue), erase(tp2, inArray, isValue)) + AndType(erase(tp1, inArray, isValue, isTyped), erase(tp2, inArray, isValue, isTyped)) case tp @ RefinedType(parent, _, _) => - erase(parent, inArray, isValue) + erase(parent, inArray, isValue, isTyped) case tref: TypeRef if tref.symbol.isPatternBound => if inArray then tref.underlying diff --git a/tests/neg-deep-subtype/enum-approx2.scala b/tests/neg-deep-subtype/enum-approx2.scala deleted file mode 100644 index bf114d9c8569..000000000000 --- a/tests/neg-deep-subtype/enum-approx2.scala +++ /dev/null @@ -1,12 +0,0 @@ -//> using options -Xfatal-warnings - -sealed trait Exp[T] -case class Fun[A, B](f: Exp[A => B]) extends Exp[A => B] - -class Test { - def eval(e: Fun[Int, Int]) = e match { - case Fun(x: Fun[Int, Double]) => ??? // error - case Fun(x: Exp[Int => String]) => ??? // error - case _ => - } -} diff --git a/tests/pending/neg/i16451.check b/tests/pending/neg/i16451.check deleted file mode 100644 index e53085e8eafa..000000000000 --- a/tests/pending/neg/i16451.check +++ /dev/null @@ -1,24 +0,0 @@ --- Error: tests/neg/i16451.scala:13:9 ---------------------------------------------------------------------------------- -13 | case x: Wrapper[Color.Red.type] => Some(x) // error - | ^ - |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] --- Error: tests/neg/i16451.scala:21:9 ---------------------------------------------------------------------------------- -21 | case x: Wrapper[Color.Red.type] => Some(x) // error - | ^ - |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Any --- Error: tests/neg/i16451.scala:25:9 ---------------------------------------------------------------------------------- -25 | case x: Wrapper[Color.Red.type] => Some(x) // error - | ^ - |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] --- Error: tests/neg/i16451.scala:29:9 ---------------------------------------------------------------------------------- -29 | case x: Wrapper[Color.Red.type] => Some(x) // error - | ^ - |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from A1 --- Error: tests/neg/i16451.scala:34:11 --------------------------------------------------------------------------------- -34 | case x: Wrapper[Color.Red.type] => x // error - | ^ - |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] --- Error: tests/neg/i16451.scala:39:11 --------------------------------------------------------------------------------- -39 | case x: Wrapper[Color.Red.type] => x // error - | ^ - |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] diff --git a/tests/warn/enum-approx2.check b/tests/warn/enum-approx2.check new file mode 100644 index 000000000000..01e1d8f5addd --- /dev/null +++ b/tests/warn/enum-approx2.check @@ -0,0 +1,20 @@ +-- [E030] Match case Unreachable Warning: tests/warn/enum-approx2.scala:7:12 ------------------------------------------- +7 | case Fun(x: Exp[Int => String]) => ??? // warn: unreachable // warn: unchecked + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Unreachable case +-- [E121] Pattern Match Warning: tests/warn/enum-approx2.scala:8:9 ----------------------------------------------------- +8 | case _ => // warn: unreachable-only-null + | ^ + | Unreachable case except for null (if this is intentional, consider writing case null => instead). +-- [E092] Pattern Match Unchecked Warning: tests/warn/enum-approx2.scala:6:13 ------------------------------------------ +6 | case Fun(x: Fun[Int, Double]) => ??? // warn: unchecked + | ^ + |the type test for Fun[Int, Double] cannot be checked at runtime because its type arguments can't be determined from Exp[Int => Int] + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/enum-approx2.scala:7:13 ------------------------------------------ +7 | case Fun(x: Exp[Int => String]) => ??? // warn: unreachable // warn: unchecked + | ^ + |the type test for Exp[Int => String] cannot be checked at runtime because its type arguments can't be determined from Exp[Int => Int] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/enum-approx2.scala b/tests/warn/enum-approx2.scala new file mode 100644 index 000000000000..2c2563a8b2f1 --- /dev/null +++ b/tests/warn/enum-approx2.scala @@ -0,0 +1,10 @@ +sealed trait Exp[T] +case class Fun[A, B](f: Exp[A => B]) extends Exp[A => B] + +class Test { + def eval(e: Fun[Int, Int]) = e match { + case Fun(x: Fun[Int, Double]) => ??? // warn: unchecked + case Fun(x: Exp[Int => String]) => ??? // warn: unreachable // warn: unchecked + case _ => // warn: unreachable-only-null + } +} diff --git a/tests/neg-deep-subtype/i11178.scala b/tests/warn/i11178.scala similarity index 94% rename from tests/neg-deep-subtype/i11178.scala rename to tests/warn/i11178.scala index 2ac4f9e07262..47e8b4c3acab 100644 --- a/tests/neg-deep-subtype/i11178.scala +++ b/tests/warn/i11178.scala @@ -1,5 +1,3 @@ -//> using options -Xfatal-warnings - trait Box[+T] case class Foo[+S](s: S) extends Box[S] diff --git a/tests/warn/i16451.check b/tests/warn/i16451.check new file mode 100644 index 000000000000..2bc469af480b --- /dev/null +++ b/tests/warn/i16451.check @@ -0,0 +1,56 @@ +-- [E030] Match case Unreachable Warning: tests/warn/i16451.scala:14:9 ------------------------------------------------- +14 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Unreachable case +-- [E030] Match case Unreachable Warning: tests/warn/i16451.scala:22:9 ------------------------------------------------- +22 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Unreachable case +-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:13:9 ------------------------------------------------ +13 | case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked + | ^ + |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:14:9 ------------------------------------------------ +14 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked + | ^ + |the type test for Wrapper[(Color.Green : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:21:9 ------------------------------------------------ +21 | case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked + | ^ + |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Any + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:22:9 ------------------------------------------------ +22 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked + | ^ + |the type test for Wrapper[(Color.Green : Color)] cannot be checked at runtime because its type arguments can't be determined from Any + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:25:9 ------------------------------------------------ +25 | case x: Wrapper[Color.Red.type] => Some(x) // error: unreachable // error: unchecked + | ^ + |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:29:9 ------------------------------------------------ +29 | case x: Wrapper[Color.Red.type] => Some(x) + | ^ + |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from A1 + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:34:11 ----------------------------------------------- +34 | case x: Wrapper[Color.Red.type] => x + | ^ + |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:39:11 ----------------------------------------------- +39 | case x: Wrapper[Color.Red.type] => x + | ^ + |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/pending/neg/i16451.scala b/tests/warn/i16451.scala similarity index 60% rename from tests/pending/neg/i16451.scala rename to tests/warn/i16451.scala index 3a93a97e1f03..b11def9ec2ba 100644 --- a/tests/pending/neg/i16451.scala +++ b/tests/warn/i16451.scala @@ -1,38 +1,42 @@ -//> using options -Werror +// enum Color: case Red, Green +//sealed trait Color +//object Color: +// case object Red extends Color +// case object Green extends Color case class Wrapper[A](value: A) object Test: def test_correct(x: Wrapper[Color]): Option[Wrapper[Color.Red.type]] = x match - case x: Wrapper[Color.Red.type] => Some(x) // error - case null => None + case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked + case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked def test_different(x: Wrapper[Color]): Option[Wrapper[Color]] = x match case x @ Wrapper(_: Color.Red.type) => Some(x) case x @ Wrapper(_: Color.Green.type) => None def test_any(x: Any): Option[Wrapper[Color.Red.type]] = x match - case x: Wrapper[Color.Red.type] => Some(x) // error - case _ => None + case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked + case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked def test_wrong(x: Wrapper[Color]): Option[Wrapper[Color.Red.type]] = x match - case x: Wrapper[Color.Red.type] => Some(x) // error + case x: Wrapper[Color.Red.type] => Some(x) // error: unreachable // error: unchecked case null => None def t2[A1 <: Wrapper[Color]](x: A1): Option[Wrapper[Color.Red.type]] = x match - case x: Wrapper[Color.Red.type] => Some(x) // error + case x: Wrapper[Color.Red.type] => Some(x) case null => None def test_wrong_seq(xs: Seq[Wrapper[Color]]): Seq[Wrapper[Color.Red.type]] = xs.collect { - case x: Wrapper[Color.Red.type] => x // error + case x: Wrapper[Color.Red.type] => x } def test_wrong_seq2(xs: Seq[Wrapper[Color]]): Seq[Wrapper[Color.Red.type]] = xs.collect { x => x match - case x: Wrapper[Color.Red.type] => x // error + case x: Wrapper[Color.Red.type] => x } def main(args: Array[String]): Unit = diff --git a/tests/warn/i5826.check b/tests/warn/i5826.check new file mode 100644 index 000000000000..d29df1c8f34b --- /dev/null +++ b/tests/warn/i5826.check @@ -0,0 +1,34 @@ +-- [E121] Pattern Match Warning: tests/warn/i5826.scala:9:9 ------------------------------------------------------------ +9 | case _ => 0 // warn: unreachable-only-null + | ^ + | Unreachable case except for null (if this is intentional, consider writing case null => instead). +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:3:9 -------------------------------------------------- +3 | case ls: List[Int] => ls.head // error, A = List[String] + | ^ + | the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from A + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:8:9 -------------------------------------------------- +8 | case ls: List[Int] => ls.head // warn: unchecked + | ^ + |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from List[String] + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:17:9 ------------------------------------------------- +17 | case ls: A[X] => 4 // error + | ^ + |the type test for Foo.this.A[X] cannot be checked at runtime because its type arguments can't be determined from Foo.this.B[X] + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:22:9 ------------------------------------------------- +22 | case ls: List[Int] => ls.head // error, List extends Int => T + | ^ + |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from A => Int + | + | longer explanation available when compiling with `-explain` +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:28:54 ------------------------------------------------ +28 | def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // error + | ^ + |the type test for Foo.this.C[String] cannot be checked at runtime because its type arguments can't be determined from Foo.this.A[T] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-deep-subtype/i5826.scala b/tests/warn/i5826.scala similarity index 88% rename from tests/neg-deep-subtype/i5826.scala rename to tests/warn/i5826.scala index 2f6bfb9f8127..e7eda8826139 100644 --- a/tests/neg-deep-subtype/i5826.scala +++ b/tests/warn/i5826.scala @@ -1,5 +1,3 @@ -//> using options -Xfatal-warnings - class Foo { def test[A]: (List[Int] | A) => Int = { case ls: List[Int] => ls.head // error, A = List[String] @@ -7,8 +5,8 @@ class Foo { } def test2: List[Int] | List[String] => Int = { - case ls: List[Int] => ls.head // error - case _ => 0 + case ls: List[Int] => ls.head // warn: unchecked + case _ => 0 // warn: unreachable-only-null } trait A[T] diff --git a/tests/neg-deep-subtype/i8932.scala b/tests/warn/i8932.scala similarity index 88% rename from tests/neg-deep-subtype/i8932.scala rename to tests/warn/i8932.scala index dc2ae3358410..84d2f7d4990a 100644 --- a/tests/neg-deep-subtype/i8932.scala +++ b/tests/warn/i8932.scala @@ -1,5 +1,3 @@ -//> using options -Xfatal-warnings - sealed trait Foo[+A] case class Bar[A]() extends Foo[A] diff --git a/tests/neg/suppressed-type-test-warnings.scala b/tests/warn/suppressed-type-test-warnings.scala similarity index 95% rename from tests/neg/suppressed-type-test-warnings.scala rename to tests/warn/suppressed-type-test-warnings.scala index 2414f13e73cb..92d86b3307e5 100644 --- a/tests/neg/suppressed-type-test-warnings.scala +++ b/tests/warn/suppressed-type-test-warnings.scala @@ -1,5 +1,3 @@ -//> using options -Xfatal-warnings - object Test { sealed trait Foo[A, B] final case class Bar[X](x: X) extends Foo[X, X] From 5d547208c4921a38de58138eb2492c947c385508 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 11 Aug 2023 16:26:10 +0100 Subject: [PATCH 5/7] Drop showAlways on UncheckedTypePattern [Cherry-picked cae678648d00411b0cb55b03448ebeb46ab71065] --- .../dotty/tools/dotc/reporting/messages.scala | 1 - tests/warn/enum-approx2.check | 8 +------- tests/warn/enum-approx2.scala | 2 +- tests/warn/i16451.check | 16 ++-------------- tests/warn/i16451.scala | 4 ++-- 5 files changed, 6 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f43b19708258..007f0a56f8d8 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -897,7 +897,6 @@ extends Message(PatternMatchExhaustivityID) { class UncheckedTypePattern(argType: Type, whyNot: String)(using Context) extends PatternMatchMsg(UncheckedTypePatternID) { - override def showAlways = true def msg(using Context) = i"the type test for $argType cannot be checked at runtime because $whyNot" def explain(using Context) = i"""|Type arguments and type refinements are erased during compile time, thus it's diff --git a/tests/warn/enum-approx2.check b/tests/warn/enum-approx2.check index 01e1d8f5addd..a75c15b424ff 100644 --- a/tests/warn/enum-approx2.check +++ b/tests/warn/enum-approx2.check @@ -1,5 +1,5 @@ -- [E030] Match case Unreachable Warning: tests/warn/enum-approx2.scala:7:12 ------------------------------------------- -7 | case Fun(x: Exp[Int => String]) => ??? // warn: unreachable // warn: unchecked +7 | case Fun(x: Exp[Int => String]) => ??? // warn: unreachable // also: unchecked (hidden) | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | Unreachable case -- [E121] Pattern Match Warning: tests/warn/enum-approx2.scala:8:9 ----------------------------------------------------- @@ -12,9 +12,3 @@ |the type test for Fun[Int, Double] cannot be checked at runtime because its type arguments can't be determined from Exp[Int => Int] | | longer explanation available when compiling with `-explain` --- [E092] Pattern Match Unchecked Warning: tests/warn/enum-approx2.scala:7:13 ------------------------------------------ -7 | case Fun(x: Exp[Int => String]) => ??? // warn: unreachable // warn: unchecked - | ^ - |the type test for Exp[Int => String] cannot be checked at runtime because its type arguments can't be determined from Exp[Int => Int] - | - | longer explanation available when compiling with `-explain` diff --git a/tests/warn/enum-approx2.scala b/tests/warn/enum-approx2.scala index 2c2563a8b2f1..38a78cd6a5e9 100644 --- a/tests/warn/enum-approx2.scala +++ b/tests/warn/enum-approx2.scala @@ -4,7 +4,7 @@ case class Fun[A, B](f: Exp[A => B]) extends Exp[A => B] class Test { def eval(e: Fun[Int, Int]) = e match { case Fun(x: Fun[Int, Double]) => ??? // warn: unchecked - case Fun(x: Exp[Int => String]) => ??? // warn: unreachable // warn: unchecked + case Fun(x: Exp[Int => String]) => ??? // warn: unreachable // also: unchecked (hidden) case _ => // warn: unreachable-only-null } } diff --git a/tests/warn/i16451.check b/tests/warn/i16451.check index 2bc469af480b..a966b5c85be4 100644 --- a/tests/warn/i16451.check +++ b/tests/warn/i16451.check @@ -1,9 +1,9 @@ -- [E030] Match case Unreachable Warning: tests/warn/i16451.scala:14:9 ------------------------------------------------- -14 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked +14 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // also: unchecked (hidden) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | Unreachable case -- [E030] Match case Unreachable Warning: tests/warn/i16451.scala:22:9 ------------------------------------------------- -22 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked +22 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // also: unchecked (hidden) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | Unreachable case -- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:13:9 ------------------------------------------------ @@ -12,24 +12,12 @@ |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] | | longer explanation available when compiling with `-explain` --- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:14:9 ------------------------------------------------ -14 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked - | ^ - |the type test for Wrapper[(Color.Green : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] - | - | longer explanation available when compiling with `-explain` -- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:21:9 ------------------------------------------------ 21 | case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked | ^ |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Any | | longer explanation available when compiling with `-explain` --- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:22:9 ------------------------------------------------ -22 | case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked - | ^ - |the type test for Wrapper[(Color.Green : Color)] cannot be checked at runtime because its type arguments can't be determined from Any - | - | longer explanation available when compiling with `-explain` -- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:25:9 ------------------------------------------------ 25 | case x: Wrapper[Color.Red.type] => Some(x) // error: unreachable // error: unchecked | ^ diff --git a/tests/warn/i16451.scala b/tests/warn/i16451.scala index b11def9ec2ba..1a83d56366f6 100644 --- a/tests/warn/i16451.scala +++ b/tests/warn/i16451.scala @@ -11,7 +11,7 @@ case class Wrapper[A](value: A) object Test: def test_correct(x: Wrapper[Color]): Option[Wrapper[Color.Red.type]] = x match case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked - case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked + case x: Wrapper[Color.Green.type] => None // warn: unreachable // also: unchecked (hidden) def test_different(x: Wrapper[Color]): Option[Wrapper[Color]] = x match case x @ Wrapper(_: Color.Red.type) => Some(x) @@ -19,7 +19,7 @@ object Test: def test_any(x: Any): Option[Wrapper[Color.Red.type]] = x match case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked - case x: Wrapper[Color.Green.type] => None // warn: unreachable // warn: unchecked + case x: Wrapper[Color.Green.type] => None // warn: unreachable // also: unchecked (hidden) def test_wrong(x: Wrapper[Color]): Option[Wrapper[Color.Red.type]] = x match case x: Wrapper[Color.Red.type] => Some(x) // error: unreachable // error: unchecked From 53d2e891897169adf14aeeb3c0037dcb81c4ac14 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 14 Aug 2023 13:06:21 +0100 Subject: [PATCH 6/7] Handle opaque aliases of arrays in Space erase [Cherry-picked 4421d12c70218aea86ae3bad6abf990756e7815b] --- .../src/dotty/tools/dotc/transform/patmat/Space.scala | 3 ++- tests/pos/i18364.Tup.scala | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i18364.Tup.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 90039422548e..b6bf8c550a57 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -458,6 +458,7 @@ object SpaceEngine { * * @param inArray whether `tp` is a type argument to `Array` * @param isValue whether `tp` is the type which match against values + * @param isTyped whether `tp` is the type from a `Typed` tree * * If `isValue` is true, then pattern-bound symbols are erased to its upper bound. * This is needed to avoid spurious unreachable warnings. See tests/patmat/i6197.scala. @@ -468,7 +469,7 @@ object SpaceEngine { WildcardType case tp @ AppliedType(tycon, args) => - val inArray = tycon.isRef(defn.ArrayClass) + val inArray = tycon.isRef(defn.ArrayClass) || tp.translucentSuperType.isRef(defn.ArrayClass) val args2 = if isTyped && !inArray then args.map(_ => WildcardType) else args.map(arg => erase(arg, inArray = inArray, isValue = false)) diff --git a/tests/pos/i18364.Tup.scala b/tests/pos/i18364.Tup.scala new file mode 100644 index 000000000000..806342934e67 --- /dev/null +++ b/tests/pos/i18364.Tup.scala @@ -0,0 +1,10 @@ +// Capturing the regression will implementing the fix for i18364 +// That broke in CI, "case _" "Unreachable case except for null" +// Because IArray is an opaque alias of Array +object Tup: + /** Convert an immutable array into a tuple of unknown arity and types */ + def fromIArray[T](xs: IArray[T]): Tuple = + val xs2: IArray[Object] = xs match + case xs: IArray[Object] @unchecked => xs + case _ => xs.map(_.asInstanceOf[Object]) + runtime.Tuples.fromIArray(xs2) From dcbc2914be650d4ce4558ae17b801ef1a3ad5ad3 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 15 Aug 2023 17:55:01 +0100 Subject: [PATCH 7/7] Vulpix: implement "// warn" tags Also, show compilation errors, if there are any, for a warn test. [Cherry-picked 3686ba06164d4db25cc12b2d688ea680f8864fab] --- .../tools/dotc/reporting/TestReporter.scala | 16 +++-- .../dotty/tools/vulpix/ParallelTesting.scala | 68 +++++++++++++++++++ tests/warn/i11178.scala | 11 ++- tests/warn/i16451.check | 8 +-- tests/warn/i16451.scala | 8 +-- tests/warn/i5826.check | 18 ++--- tests/warn/i5826.scala | 9 ++- tests/warn/i8932.scala | 4 +- .../warn/suppressed-type-test-warnings.scala | 8 +-- 9 files changed, 105 insertions(+), 45 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index 940fc875a021..c0ece68e3b46 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -18,11 +18,12 @@ import interfaces.Diagnostic.{ERROR, WARNING} import scala.io.Codec -class TestReporter protected (outWriter: PrintWriter, filePrintln: String => Unit, logLevel: Int) +class TestReporter protected (outWriter: PrintWriter, logLevel: Int) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering { - protected final val _errorBuf = mutable.ArrayBuffer.empty[Diagnostic] - final def errors: Iterator[Diagnostic] = _errorBuf.iterator + protected final val _diagnosticBuf = mutable.ArrayBuffer.empty[Diagnostic] + final def diagnostics: Iterator[Diagnostic] = _diagnosticBuf.iterator + final def errors: Iterator[Diagnostic] = diagnostics.filter(_.level >= ERROR) protected final val _messageBuf = mutable.ArrayBuffer.empty[String] final def messages: Iterator[String] = _messageBuf.iterator @@ -79,8 +80,9 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M case _ => "" } - if dia.level >= ERROR then _errorBuf.append(dia) - if dia.level >= WARNING then _consoleReporter.doReport(dia) + if dia.level >= WARNING then + _diagnosticBuf.append(dia) + _consoleReporter.doReport(dia) printMessageAndPos(dia, extra) } } @@ -125,10 +127,10 @@ object TestReporter { } def reporter(ps: PrintStream, logLevel: Int): TestReporter = - new TestReporter(new PrintWriter(ps, true), logPrintln, logLevel) + new TestReporter(new PrintWriter(ps, true), logLevel) def simplifiedReporter(writer: PrintWriter): TestReporter = { - val rep = new TestReporter(writer, logPrintln, WARNING) { + val rep = new TestReporter(writer, WARNING) { /** Prints the message with the given position indication in a simplified manner */ override def printMessageAndPos(dia: Diagnostic, extra: String)(using Context): Unit = { def report() = { diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index bcf17c37fa0b..4e6fe67aec37 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -729,6 +729,74 @@ trait ParallelTesting extends RunnerOrchestration { self => override def onSuccess(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable): Unit = diffCheckfile(testSource, reporters, logger) + override def maybeFailureMessage(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] = + lazy val (map, expCount) = getWarnMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq) + lazy val obtCount = reporters.foldLeft(0)(_ + _.warningCount) + lazy val (expected, unexpected) = getMissingExpectedWarnings(map, reporters.iterator.flatMap(_.diagnostics)) + def hasMissingAnnotations = expected.nonEmpty || unexpected.nonEmpty + def showDiagnostics = "-> following the diagnostics:\n" + + reporters.flatMap(_.diagnostics.toSeq.sortBy(_.pos.line).map(e => s"${e.pos.line + 1}: ${e.message}")).mkString(" at ", "\n at ", "") + Option: + if reporters.exists(_.compilerCrashed) then s"Compiler crashed when compiling: ${testSource.title}" + else if reporters.exists(_.errorCount > 0) then + s"""Compilation failed for: ${testSource.title} + |$showDiagnostics + |""".stripMargin.trim.linesIterator.mkString("\n", "\n", "") + else if obtCount == 0 then s"\nNo warnings found when compiling warn test $testSource" + else if expCount == 0 then s"\nNo warning expected/defined in $testSource -- use // warn" + else if expCount != obtCount then + s"""|Wrong number of warnings encountered when compiling $testSource + |expected: $expCount, actual: $obtCount + |${expected.mkString("Unfulfilled expectations:\n", "\n", "")} + |${unexpected.mkString("Unexpected warnings:\n", "\n", "")} + |$showDiagnostics + |""".stripMargin.trim.linesIterator.mkString("\n", "\n", "") + else if hasMissingAnnotations then s"\nWarnings found on incorrect row numbers when compiling $testSource\n$showDiagnostics" + else if !map.isEmpty then s"\nExpected warnings(s) have {=}: $map" + else null + end maybeFailureMessage + + def getWarnMapAndExpectedCount(files: Seq[JFile]): (HashMap[String, Integer], Int) = + val comment = raw"//( *)warn".r + val map = new HashMap[String, Integer]() + var count = 0 + def bump(key: String): Unit = + map.get(key) match + case null => map.put(key, 1) + case n => map.put(key, n+1) + count += 1 + files.filter(isSourceFile).foreach { file => + Using(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => + source.getLines.zipWithIndex.foreach { case (line, lineNbr) => + comment.findAllMatchIn(line).foreach { _ => + bump(s"${file.getPath}:${lineNbr+1}") + } + } + }.get + } + (map, count) + + def getMissingExpectedWarnings(map: HashMap[String, Integer], reporterWarnings: Iterator[Diagnostic]): (List[String], List[String]) = + val unexpected, unpositioned = ListBuffer.empty[String] + def relativize(path: String): String = path.split(JFile.separatorChar).dropWhile(_ != "tests").mkString(JFile.separator) + def seenAt(key: String): Boolean = + map.get(key) match + case null => false + case 1 => map.remove(key) ; true + case n => map.put(key, n - 1) ; true + def sawDiagnostic(d: Diagnostic): Unit = + val srcpos = d.pos.nonInlined + if srcpos.exists then + val key = s"${relativize(srcpos.source.file.toString())}:${srcpos.line + 1}" + if !seenAt(key) then unexpected += key + else + unpositioned += relativize(srcpos.source.file.toString()) + + reporterWarnings.foreach(sawDiagnostic) + + (map.asScala.keys.toList, (unexpected ++ unpositioned).toList) + end getMissingExpectedWarnings + private final class RewriteTest(testSources: List[TestSource], checkFiles: Map[JFile, JFile], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) extends Test(testSources, times, threadLimit, suppressAllOutput) { private def verifyOutput(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable) = { diff --git a/tests/warn/i11178.scala b/tests/warn/i11178.scala index 47e8b4c3acab..a59b899be365 100644 --- a/tests/warn/i11178.scala +++ b/tests/warn/i11178.scala @@ -3,7 +3,7 @@ case class Foo[+S](s: S) extends Box[S] def unwrap2[A](b: Box[A]): A = b match - case _: Foo[Int] => 0 // error + case _: Foo[Int] => 0 // warn object Test1 { // Invariant case, OK @@ -11,8 +11,7 @@ object Test1 { def test[A](bar: Bar[A]) = bar match { - case _: Bar[Boolean] => ??? // error - case _ => ??? + case _: Bar[Boolean] => ??? // warn } } @@ -22,8 +21,7 @@ object Test2 { def test[A](bar: Bar[A]) = bar match { - case _: Bar[Boolean] => ??? // error - case _ => ??? + case _: Bar[Boolean] => ??? // warn } } @@ -33,7 +31,6 @@ object Test3 { def test[A](bar: Bar[A]) = bar match { - case _: Bar[Boolean] => ??? // error - case _ => ??? + case _: Bar[Boolean] => ??? // warn } } diff --git a/tests/warn/i16451.check b/tests/warn/i16451.check index a966b5c85be4..09c2a7df8179 100644 --- a/tests/warn/i16451.check +++ b/tests/warn/i16451.check @@ -19,25 +19,25 @@ | | longer explanation available when compiling with `-explain` -- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:25:9 ------------------------------------------------ -25 | case x: Wrapper[Color.Red.type] => Some(x) // error: unreachable // error: unchecked +25 | case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked | ^ |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] | | longer explanation available when compiling with `-explain` -- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:29:9 ------------------------------------------------ -29 | case x: Wrapper[Color.Red.type] => Some(x) +29 | case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked | ^ |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from A1 | | longer explanation available when compiling with `-explain` -- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:34:11 ----------------------------------------------- -34 | case x: Wrapper[Color.Red.type] => x +34 | case x: Wrapper[Color.Red.type] => x // warn: unchecked | ^ |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] | | longer explanation available when compiling with `-explain` -- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:39:11 ----------------------------------------------- -39 | case x: Wrapper[Color.Red.type] => x +39 | case x: Wrapper[Color.Red.type] => x // warn: unchecked | ^ |the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color] | diff --git a/tests/warn/i16451.scala b/tests/warn/i16451.scala index 1a83d56366f6..138af3632772 100644 --- a/tests/warn/i16451.scala +++ b/tests/warn/i16451.scala @@ -22,21 +22,21 @@ object Test: case x: Wrapper[Color.Green.type] => None // warn: unreachable // also: unchecked (hidden) def test_wrong(x: Wrapper[Color]): Option[Wrapper[Color.Red.type]] = x match - case x: Wrapper[Color.Red.type] => Some(x) // error: unreachable // error: unchecked + case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked case null => None def t2[A1 <: Wrapper[Color]](x: A1): Option[Wrapper[Color.Red.type]] = x match - case x: Wrapper[Color.Red.type] => Some(x) + case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked case null => None def test_wrong_seq(xs: Seq[Wrapper[Color]]): Seq[Wrapper[Color.Red.type]] = xs.collect { - case x: Wrapper[Color.Red.type] => x + case x: Wrapper[Color.Red.type] => x // warn: unchecked } def test_wrong_seq2(xs: Seq[Wrapper[Color]]): Seq[Wrapper[Color.Red.type]] = xs.collect { x => x match - case x: Wrapper[Color.Red.type] => x + case x: Wrapper[Color.Red.type] => x // warn: unchecked } def main(args: Array[String]): Unit = diff --git a/tests/warn/i5826.check b/tests/warn/i5826.check index d29df1c8f34b..18ff50a933cb 100644 --- a/tests/warn/i5826.check +++ b/tests/warn/i5826.check @@ -1,9 +1,5 @@ --- [E121] Pattern Match Warning: tests/warn/i5826.scala:9:9 ------------------------------------------------------------ -9 | case _ => 0 // warn: unreachable-only-null - | ^ - | Unreachable case except for null (if this is intentional, consider writing case null => instead). -- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:3:9 -------------------------------------------------- -3 | case ls: List[Int] => ls.head // error, A = List[String] +3 | case ls: List[Int] => ls.head // warn: unchecked | ^ | the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from A | @@ -14,20 +10,20 @@ |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from List[String] | | longer explanation available when compiling with `-explain` --- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:17:9 ------------------------------------------------- -17 | case ls: A[X] => 4 // error +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:16:9 ------------------------------------------------- +16 | case ls: A[X] => 4 // warn | ^ |the type test for Foo.this.A[X] cannot be checked at runtime because its type arguments can't be determined from Foo.this.B[X] | | longer explanation available when compiling with `-explain` --- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:22:9 ------------------------------------------------- -22 | case ls: List[Int] => ls.head // error, List extends Int => T +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:21:9 ------------------------------------------------- +21 | case ls: List[Int] => ls.head // warn, List extends Int => T | ^ |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from A => Int | | longer explanation available when compiling with `-explain` --- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:28:54 ------------------------------------------------ -28 | def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // error +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:27:54 ------------------------------------------------ +27 | def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // warn | ^ |the type test for Foo.this.C[String] cannot be checked at runtime because its type arguments can't be determined from Foo.this.A[T] | diff --git a/tests/warn/i5826.scala b/tests/warn/i5826.scala index e7eda8826139..f54d6e58d033 100644 --- a/tests/warn/i5826.scala +++ b/tests/warn/i5826.scala @@ -1,12 +1,11 @@ class Foo { def test[A]: (List[Int] | A) => Int = { - case ls: List[Int] => ls.head // error, A = List[String] + case ls: List[Int] => ls.head // warn: unchecked case _ => 0 } def test2: List[Int] | List[String] => Int = { case ls: List[Int] => ls.head // warn: unchecked - case _ => 0 // warn: unreachable-only-null } trait A[T] @@ -14,18 +13,18 @@ class Foo { // suppose: class C extends A[Int] with B[String] def test3[X]: A[X] | B[X] => Int = { - case ls: A[X] => 4 // error + case ls: A[X] => 4 // warn case _ => 0 } def test4[A](x: List[Int] | (A => Int)) = x match { - case ls: List[Int] => ls.head // error, List extends Int => T + case ls: List[Int] => ls.head // warn, List extends Int => T case _ => 0 } final class C[T] extends A[T] - def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // error + def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // warn def test6[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[T]] diff --git a/tests/warn/i8932.scala b/tests/warn/i8932.scala index 84d2f7d4990a..95a4e86e9791 100644 --- a/tests/warn/i8932.scala +++ b/tests/warn/i8932.scala @@ -5,8 +5,8 @@ class Dummy extends Bar[Nothing] with Foo[String] def bugReport[A](foo: Foo[A]): Foo[A] = foo match { - case bar: Bar[A] => bar // error - case dummy: Dummy => ??? + case bar: Bar[A] => bar // warn: unchecked + case dummy: Dummy => ??? // warn: unreachable } def test = bugReport(new Dummy: Foo[String]) diff --git a/tests/warn/suppressed-type-test-warnings.scala b/tests/warn/suppressed-type-test-warnings.scala index 92d86b3307e5..c78e8e263153 100644 --- a/tests/warn/suppressed-type-test-warnings.scala +++ b/tests/warn/suppressed-type-test-warnings.scala @@ -11,19 +11,17 @@ object Test { } def err1[A, B](value: Foo[A, B], a: A => Int): B = value match { - case b: Bar[A] => // spurious // error + case b: Bar[A] => // spurious // warn b.x } def err2[A, B](value: Foo[A, B], a: A => Int): B = value match { - case b: Bar[B] => // spurious // error + case b: Bar[B] => // spurious // warn b.x - case _ => ??? // avoid fatal inexhaustivity warnings suppressing the uncheckable warning } def fail[A, B](value: Foo[A, B], a: A => Int): B = value match { - case b: Bar[Int] => // error + case b: Bar[Int] => // warn b.x - case _ => ??? // avoid fatal inexhaustivity warnings suppressing the uncheckable warning } }