Skip to content

Commit dcbc291

Browse files
dwijnandWojciechMazur
authored andcommitted
Vulpix: implement "// warn" tags
Also, show compilation errors, if there are any, for a warn test. [Cherry-picked 3686ba0]
1 parent 53d2e89 commit dcbc291

File tree

9 files changed

+105
-45
lines changed

9 files changed

+105
-45
lines changed

compiler/test/dotty/tools/dotc/reporting/TestReporter.scala

+9-7
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ import interfaces.Diagnostic.{ERROR, WARNING}
1818

1919
import scala.io.Codec
2020

21-
class TestReporter protected (outWriter: PrintWriter, filePrintln: String => Unit, logLevel: Int)
21+
class TestReporter protected (outWriter: PrintWriter, logLevel: Int)
2222
extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering {
2323

24-
protected final val _errorBuf = mutable.ArrayBuffer.empty[Diagnostic]
25-
final def errors: Iterator[Diagnostic] = _errorBuf.iterator
24+
protected final val _diagnosticBuf = mutable.ArrayBuffer.empty[Diagnostic]
25+
final def diagnostics: Iterator[Diagnostic] = _diagnosticBuf.iterator
26+
final def errors: Iterator[Diagnostic] = diagnostics.filter(_.level >= ERROR)
2627

2728
protected final val _messageBuf = mutable.ArrayBuffer.empty[String]
2829
final def messages: Iterator[String] = _messageBuf.iterator
@@ -79,8 +80,9 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
7980
case _ => ""
8081
}
8182

82-
if dia.level >= ERROR then _errorBuf.append(dia)
83-
if dia.level >= WARNING then _consoleReporter.doReport(dia)
83+
if dia.level >= WARNING then
84+
_diagnosticBuf.append(dia)
85+
_consoleReporter.doReport(dia)
8486
printMessageAndPos(dia, extra)
8587
}
8688
}
@@ -125,10 +127,10 @@ object TestReporter {
125127
}
126128

127129
def reporter(ps: PrintStream, logLevel: Int): TestReporter =
128-
new TestReporter(new PrintWriter(ps, true), logPrintln, logLevel)
130+
new TestReporter(new PrintWriter(ps, true), logLevel)
129131

130132
def simplifiedReporter(writer: PrintWriter): TestReporter = {
131-
val rep = new TestReporter(writer, logPrintln, WARNING) {
133+
val rep = new TestReporter(writer, WARNING) {
132134
/** Prints the message with the given position indication in a simplified manner */
133135
override def printMessageAndPos(dia: Diagnostic, extra: String)(using Context): Unit = {
134136
def report() = {

compiler/test/dotty/tools/vulpix/ParallelTesting.scala

+68
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,74 @@ trait ParallelTesting extends RunnerOrchestration { self =>
729729
override def onSuccess(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable): Unit =
730730
diffCheckfile(testSource, reporters, logger)
731731

732+
override def maybeFailureMessage(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] =
733+
lazy val (map, expCount) = getWarnMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq)
734+
lazy val obtCount = reporters.foldLeft(0)(_ + _.warningCount)
735+
lazy val (expected, unexpected) = getMissingExpectedWarnings(map, reporters.iterator.flatMap(_.diagnostics))
736+
def hasMissingAnnotations = expected.nonEmpty || unexpected.nonEmpty
737+
def showDiagnostics = "-> following the diagnostics:\n" +
738+
reporters.flatMap(_.diagnostics.toSeq.sortBy(_.pos.line).map(e => s"${e.pos.line + 1}: ${e.message}")).mkString(" at ", "\n at ", "")
739+
Option:
740+
if reporters.exists(_.compilerCrashed) then s"Compiler crashed when compiling: ${testSource.title}"
741+
else if reporters.exists(_.errorCount > 0) then
742+
s"""Compilation failed for: ${testSource.title}
743+
|$showDiagnostics
744+
|""".stripMargin.trim.linesIterator.mkString("\n", "\n", "")
745+
else if obtCount == 0 then s"\nNo warnings found when compiling warn test $testSource"
746+
else if expCount == 0 then s"\nNo warning expected/defined in $testSource -- use // warn"
747+
else if expCount != obtCount then
748+
s"""|Wrong number of warnings encountered when compiling $testSource
749+
|expected: $expCount, actual: $obtCount
750+
|${expected.mkString("Unfulfilled expectations:\n", "\n", "")}
751+
|${unexpected.mkString("Unexpected warnings:\n", "\n", "")}
752+
|$showDiagnostics
753+
|""".stripMargin.trim.linesIterator.mkString("\n", "\n", "")
754+
else if hasMissingAnnotations then s"\nWarnings found on incorrect row numbers when compiling $testSource\n$showDiagnostics"
755+
else if !map.isEmpty then s"\nExpected warnings(s) have {<warning position>=<unreported warning>}: $map"
756+
else null
757+
end maybeFailureMessage
758+
759+
def getWarnMapAndExpectedCount(files: Seq[JFile]): (HashMap[String, Integer], Int) =
760+
val comment = raw"//( *)warn".r
761+
val map = new HashMap[String, Integer]()
762+
var count = 0
763+
def bump(key: String): Unit =
764+
map.get(key) match
765+
case null => map.put(key, 1)
766+
case n => map.put(key, n+1)
767+
count += 1
768+
files.filter(isSourceFile).foreach { file =>
769+
Using(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source =>
770+
source.getLines.zipWithIndex.foreach { case (line, lineNbr) =>
771+
comment.findAllMatchIn(line).foreach { _ =>
772+
bump(s"${file.getPath}:${lineNbr+1}")
773+
}
774+
}
775+
}.get
776+
}
777+
(map, count)
778+
779+
def getMissingExpectedWarnings(map: HashMap[String, Integer], reporterWarnings: Iterator[Diagnostic]): (List[String], List[String]) =
780+
val unexpected, unpositioned = ListBuffer.empty[String]
781+
def relativize(path: String): String = path.split(JFile.separatorChar).dropWhile(_ != "tests").mkString(JFile.separator)
782+
def seenAt(key: String): Boolean =
783+
map.get(key) match
784+
case null => false
785+
case 1 => map.remove(key) ; true
786+
case n => map.put(key, n - 1) ; true
787+
def sawDiagnostic(d: Diagnostic): Unit =
788+
val srcpos = d.pos.nonInlined
789+
if srcpos.exists then
790+
val key = s"${relativize(srcpos.source.file.toString())}:${srcpos.line + 1}"
791+
if !seenAt(key) then unexpected += key
792+
else
793+
unpositioned += relativize(srcpos.source.file.toString())
794+
795+
reporterWarnings.foreach(sawDiagnostic)
796+
797+
(map.asScala.keys.toList, (unexpected ++ unpositioned).toList)
798+
end getMissingExpectedWarnings
799+
732800
private final class RewriteTest(testSources: List[TestSource], checkFiles: Map[JFile, JFile], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting)
733801
extends Test(testSources, times, threadLimit, suppressAllOutput) {
734802
private def verifyOutput(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable) = {

tests/warn/i11178.scala

+4-7
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ case class Foo[+S](s: S) extends Box[S]
33

44
def unwrap2[A](b: Box[A]): A =
55
b match
6-
case _: Foo[Int] => 0 // error
6+
case _: Foo[Int] => 0 // warn
77

88
object Test1 {
99
// Invariant case, OK
1010
sealed trait Bar[A]
1111

1212
def test[A](bar: Bar[A]) =
1313
bar match {
14-
case _: Bar[Boolean] => ??? // error
15-
case _ => ???
14+
case _: Bar[Boolean] => ??? // warn
1615
}
1716
}
1817

@@ -22,8 +21,7 @@ object Test2 {
2221

2322
def test[A](bar: Bar[A]) =
2423
bar match {
25-
case _: Bar[Boolean] => ??? // error
26-
case _ => ???
24+
case _: Bar[Boolean] => ??? // warn
2725
}
2826
}
2927

@@ -33,7 +31,6 @@ object Test3 {
3331

3432
def test[A](bar: Bar[A]) =
3533
bar match {
36-
case _: Bar[Boolean] => ??? // error
37-
case _ => ???
34+
case _: Bar[Boolean] => ??? // warn
3835
}
3936
}

tests/warn/i16451.check

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,25 @@
1919
|
2020
| longer explanation available when compiling with `-explain`
2121
-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:25:9 ------------------------------------------------
22-
25 | case x: Wrapper[Color.Red.type] => Some(x) // error: unreachable // error: unchecked
22+
25 | case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked
2323
| ^
2424
|the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color]
2525
|
2626
| longer explanation available when compiling with `-explain`
2727
-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:29:9 ------------------------------------------------
28-
29 | case x: Wrapper[Color.Red.type] => Some(x)
28+
29 | case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked
2929
| ^
3030
|the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from A1
3131
|
3232
| longer explanation available when compiling with `-explain`
3333
-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:34:11 -----------------------------------------------
34-
34 | case x: Wrapper[Color.Red.type] => x
34+
34 | case x: Wrapper[Color.Red.type] => x // warn: unchecked
3535
| ^
3636
|the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color]
3737
|
3838
| longer explanation available when compiling with `-explain`
3939
-- [E092] Pattern Match Unchecked Warning: tests/warn/i16451.scala:39:11 -----------------------------------------------
40-
39 | case x: Wrapper[Color.Red.type] => x
40+
39 | case x: Wrapper[Color.Red.type] => x // warn: unchecked
4141
| ^
4242
|the type test for Wrapper[(Color.Red : Color)] cannot be checked at runtime because its type arguments can't be determined from Wrapper[Color]
4343
|

tests/warn/i16451.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,21 @@ object Test:
2222
case x: Wrapper[Color.Green.type] => None // warn: unreachable // also: unchecked (hidden)
2323

2424
def test_wrong(x: Wrapper[Color]): Option[Wrapper[Color.Red.type]] = x match
25-
case x: Wrapper[Color.Red.type] => Some(x) // error: unreachable // error: unchecked
25+
case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked
2626
case null => None
2727

2828
def t2[A1 <: Wrapper[Color]](x: A1): Option[Wrapper[Color.Red.type]] = x match
29-
case x: Wrapper[Color.Red.type] => Some(x)
29+
case x: Wrapper[Color.Red.type] => Some(x) // warn: unchecked
3030
case null => None
3131

3232
def test_wrong_seq(xs: Seq[Wrapper[Color]]): Seq[Wrapper[Color.Red.type]] =
3333
xs.collect {
34-
case x: Wrapper[Color.Red.type] => x
34+
case x: Wrapper[Color.Red.type] => x // warn: unchecked
3535
}
3636

3737
def test_wrong_seq2(xs: Seq[Wrapper[Color]]): Seq[Wrapper[Color.Red.type]] =
3838
xs.collect { x => x match
39-
case x: Wrapper[Color.Red.type] => x
39+
case x: Wrapper[Color.Red.type] => x // warn: unchecked
4040
}
4141

4242
def main(args: Array[String]): Unit =

tests/warn/i5826.check

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
-- [E121] Pattern Match Warning: tests/warn/i5826.scala:9:9 ------------------------------------------------------------
2-
9 | case _ => 0 // warn: unreachable-only-null
3-
| ^
4-
| Unreachable case except for null (if this is intentional, consider writing case null => instead).
51
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:3:9 --------------------------------------------------
6-
3 | case ls: List[Int] => ls.head // error, A = List[String]
2+
3 | case ls: List[Int] => ls.head // warn: unchecked
73
| ^
84
| the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from A
95
|
@@ -14,20 +10,20 @@
1410
|the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from List[String]
1511
|
1612
| longer explanation available when compiling with `-explain`
17-
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:17:9 -------------------------------------------------
18-
17 | case ls: A[X] => 4 // error
13+
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:16:9 -------------------------------------------------
14+
16 | case ls: A[X] => 4 // warn
1915
| ^
2016
|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]
2117
|
2218
| longer explanation available when compiling with `-explain`
23-
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:22:9 -------------------------------------------------
24-
22 | case ls: List[Int] => ls.head // error, List extends Int => T
19+
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:21:9 -------------------------------------------------
20+
21 | case ls: List[Int] => ls.head // warn, List extends Int => T
2521
| ^
2622
|the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from A => Int
2723
|
2824
| longer explanation available when compiling with `-explain`
29-
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:28:54 ------------------------------------------------
30-
28 | def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // error
25+
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:27:54 ------------------------------------------------
26+
27 | def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // warn
3127
| ^
3228
|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]
3329
|

tests/warn/i5826.scala

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
11
class Foo {
22
def test[A]: (List[Int] | A) => Int = {
3-
case ls: List[Int] => ls.head // error, A = List[String]
3+
case ls: List[Int] => ls.head // warn: unchecked
44
case _ => 0
55
}
66

77
def test2: List[Int] | List[String] => Int = {
88
case ls: List[Int] => ls.head // warn: unchecked
9-
case _ => 0 // warn: unreachable-only-null
109
}
1110

1211
trait A[T]
1312
trait B[T]
1413

1514
// suppose: class C extends A[Int] with B[String]
1615
def test3[X]: A[X] | B[X] => Int = {
17-
case ls: A[X] => 4 // error
16+
case ls: A[X] => 4 // warn
1817
case _ => 0
1918
}
2019

2120
def test4[A](x: List[Int] | (A => Int)) = x match {
22-
case ls: List[Int] => ls.head // error, List extends Int => T
21+
case ls: List[Int] => ls.head // warn, List extends Int => T
2322
case _ => 0
2423
}
2524

2625
final class C[T] extends A[T]
2726

28-
def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // error
27+
def test5[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[String]] // warn
2928

3029
def test6[T](x: A[T] | B[T] | Option[T]): Boolean = x.isInstanceOf[C[T]]
3130

tests/warn/i8932.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ class Dummy extends Bar[Nothing] with Foo[String]
55

66
def bugReport[A](foo: Foo[A]): Foo[A] =
77
foo match {
8-
case bar: Bar[A] => bar // error
9-
case dummy: Dummy => ???
8+
case bar: Bar[A] => bar // warn: unchecked
9+
case dummy: Dummy => ??? // warn: unreachable
1010
}
1111

1212
def test = bugReport(new Dummy: Foo[String])

tests/warn/suppressed-type-test-warnings.scala

+3-5
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,17 @@ object Test {
1111
}
1212

1313
def err1[A, B](value: Foo[A, B], a: A => Int): B = value match {
14-
case b: Bar[A] => // spurious // error
14+
case b: Bar[A] => // spurious // warn
1515
b.x
1616
}
1717

1818
def err2[A, B](value: Foo[A, B], a: A => Int): B = value match {
19-
case b: Bar[B] => // spurious // error
19+
case b: Bar[B] => // spurious // warn
2020
b.x
21-
case _ => ??? // avoid fatal inexhaustivity warnings suppressing the uncheckable warning
2221
}
2322

2423
def fail[A, B](value: Foo[A, B], a: A => Int): B = value match {
25-
case b: Bar[Int] => // error
24+
case b: Bar[Int] => // warn
2625
b.x
27-
case _ => ??? // avoid fatal inexhaustivity warnings suppressing the uncheckable warning
2826
}
2927
}

0 commit comments

Comments
 (0)