Skip to content

Commit

Permalink
Vulpix: implement "// warn" tags
Browse files Browse the repository at this point in the history
Also, show compilation errors, if there are any, for a warn test.

[Cherry-picked 3686ba0]
  • Loading branch information
dwijnand authored and WojciechMazur committed Jun 19, 2024
1 parent 982b70b commit b32aa1d
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 45 deletions.
16 changes: 9 additions & 7 deletions compiler/test/dotty/tools/dotc/reporting/TestReporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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() = {
Expand Down
68 changes: 68 additions & 0 deletions compiler/test/dotty/tools/vulpix/ParallelTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {<warning position>=<unreported warning>}: $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) = {
Expand Down
11 changes: 4 additions & 7 deletions tests/warn/i11178.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ 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
sealed trait Bar[A]

def test[A](bar: Bar[A]) =
bar match {
case _: Bar[Boolean] => ??? // error
case _ => ???
case _: Bar[Boolean] => ??? // warn
}
}

Expand All @@ -22,8 +21,7 @@ object Test2 {

def test[A](bar: Bar[A]) =
bar match {
case _: Bar[Boolean] => ??? // error
case _ => ???
case _: Bar[Boolean] => ??? // warn
}
}

Expand All @@ -33,7 +31,6 @@ object Test3 {

def test[A](bar: Bar[A]) =
bar match {
case _: Bar[Boolean] => ??? // error
case _ => ???
case _: Bar[Boolean] => ??? // warn
}
}
8 changes: 4 additions & 4 deletions tests/warn/i16451.check
Original file line number Diff line number Diff line change
Expand Up @@ -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]
|
Expand Down
8 changes: 4 additions & 4 deletions tests/warn/i16451.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
18 changes: 7 additions & 11 deletions tests/warn/i5826.check
Original file line number Diff line number Diff line change
@@ -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
|
Expand All @@ -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]
|
Expand Down
9 changes: 4 additions & 5 deletions tests/warn/i5826.scala
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
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]
trait B[T]

// 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]]

Expand Down
4 changes: 2 additions & 2 deletions tests/warn/i8932.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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])
8 changes: 3 additions & 5 deletions tests/warn/suppressed-type-test-warnings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit b32aa1d

Please sign in to comment.