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 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..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) @@ -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/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 2464ca448763..b6bf8c550a57 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) @@ -458,28 +458,31 @@ 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. */ - 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 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)) 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/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/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 bccbcbee29e1..4e6fe67aec37 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 @@ -717,6 +723,80 @@ 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) + + 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) = { @@ -808,10 +888,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 +1091,11 @@ 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() + def checkCompile()(implicit summaryReport: SummaryReporting): this.type = + checkPass(new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput), "Pos") - 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 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 @@ -1047,35 +1115,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 +1149,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 !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 test.didFail then - fail("Rewrite test failed") + 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 = { 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/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) |^^^^^^^^^^^^^^^ 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/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) diff --git a/tests/warn/enum-approx2.check b/tests/warn/enum-approx2.check new file mode 100644 index 000000000000..a75c15b424ff --- /dev/null +++ b/tests/warn/enum-approx2.check @@ -0,0 +1,14 @@ +-- [E030] Match case Unreachable Warning: tests/warn/enum-approx2.scala:7:12 ------------------------------------------- +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 ----------------------------------------------------- +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` diff --git a/tests/warn/enum-approx2.scala b/tests/warn/enum-approx2.scala new file mode 100644 index 000000000000..38a78cd6a5e9 --- /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 // also: unchecked (hidden) + case _ => // warn: unreachable-only-null + } +} diff --git a/tests/neg-deep-subtype/i11178.scala b/tests/warn/i11178.scala similarity index 63% rename from tests/neg-deep-subtype/i11178.scala rename to tests/warn/i11178.scala index 2ac4f9e07262..a59b899be365 100644 --- a/tests/neg-deep-subtype/i11178.scala +++ b/tests/warn/i11178.scala @@ -1,11 +1,9 @@ -//> using options -Xfatal-warnings - trait Box[+T] 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 @@ -13,8 +11,7 @@ object Test1 { def test[A](bar: Bar[A]) = bar match { - case _: Bar[Boolean] => ??? // error - case _ => ??? + case _: Bar[Boolean] => ??? // warn } } @@ -24,8 +21,7 @@ object Test2 { def test[A](bar: Bar[A]) = bar match { - case _: Bar[Boolean] => ??? // error - case _ => ??? + case _: Bar[Boolean] => ??? // warn } } @@ -35,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 new file mode 100644 index 000000000000..09c2a7df8179 --- /dev/null +++ b/tests/warn/i16451.check @@ -0,0 +1,44 @@ +-- [E030] Match case Unreachable Warning: tests/warn/i16451.scala:14:9 ------------------------------------------------- +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 // also: unchecked (hidden) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | 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: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:25:9 ------------------------------------------------ +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) // 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 // 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 // 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` diff --git a/tests/pending/neg/i16451.scala b/tests/warn/i16451.scala similarity index 58% rename from tests/pending/neg/i16451.scala rename to tests/warn/i16451.scala index 3a93a97e1f03..138af3632772 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 // also: unchecked (hidden) 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 // 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 + 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) // error + 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 // error + 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 // error + 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 new file mode 100644 index 000000000000..18ff50a933cb --- /dev/null +++ b/tests/warn/i5826.check @@ -0,0 +1,30 @@ +-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:3:9 -------------------------------------------------- +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 + | + | 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: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: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: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] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-deep-subtype/i5826.scala b/tests/warn/i5826.scala similarity index 72% rename from tests/neg-deep-subtype/i5826.scala rename to tests/warn/i5826.scala index 2f6bfb9f8127..f54d6e58d033 100644 --- a/tests/neg-deep-subtype/i5826.scala +++ b/tests/warn/i5826.scala @@ -1,14 +1,11 @@ -//> using options -Xfatal-warnings - 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 // error - case _ => 0 + case ls: List[Int] => ls.head // warn: unchecked } trait A[T] @@ -16,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/neg-deep-subtype/i8932.scala b/tests/warn/i8932.scala similarity index 66% rename from tests/neg-deep-subtype/i8932.scala rename to tests/warn/i8932.scala index dc2ae3358410..95a4e86e9791 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] @@ -7,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/neg/suppressed-type-test-warnings.scala b/tests/warn/suppressed-type-test-warnings.scala similarity index 60% rename from tests/neg/suppressed-type-test-warnings.scala rename to tests/warn/suppressed-type-test-warnings.scala index 2414f13e73cb..c78e8e263153 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] @@ -13,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 } }