Skip to content

Commit ff69a3b

Browse files
committed
add test set for exhaustivity and redundancy check
1 parent d02b2eb commit ff69a3b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+2610
-0
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package test.transform
2+
3+
import java.io._
4+
5+
import scala.io.Source._
6+
import scala.reflect.io.Directory
7+
import org.junit.Test
8+
import dotty.tools.dotc.Main
9+
import dotty.tools.dotc.reporting.ConsoleReporter
10+
11+
class PatmatExhaustivityTest {
12+
val testsDir = "./tests/patmat"
13+
// stop-after: patmatexhaust-huge.scala crash compiler
14+
val options = List("-Ystop-after:splitter")
15+
16+
private def compileFile(file: File) = {
17+
val stringBuffer = new StringWriter()
18+
val reporter = new ConsoleReporter(writer = new PrintWriter(stringBuffer))
19+
20+
try {
21+
Main.process((file.getPath::options).toArray, reporter, null)
22+
} catch {
23+
case e: Throwable =>
24+
println(s"Compile $file exception:")
25+
e.printStackTrace()
26+
}
27+
28+
val actual = stringBuffer.toString.trim
29+
val checkFilePath = file.getAbsolutePath.stripSuffix(".scala") + ".check"
30+
val checkContent =
31+
if (new File(checkFilePath).exists)
32+
fromFile(checkFilePath).getLines.mkString("\n").trim
33+
else ""
34+
35+
(file, checkContent, actual)
36+
}
37+
38+
/** A single test with multiple files grouped in a folder */
39+
private def compileDir(file: File) = {
40+
val stringBuffer = new StringWriter()
41+
val reporter = new ConsoleReporter(writer = new PrintWriter(stringBuffer))
42+
43+
val files = Directory(file.getPath).list.toList
44+
.filter(f => f.extension == "scala" || f.extension == "java" )
45+
.map(_.jfile.getPath)
46+
47+
try {
48+
Main.process((options ++ files).toArray, reporter, null)
49+
} catch {
50+
case e: Throwable =>
51+
println(s"Compile $file exception:")
52+
e.printStackTrace()
53+
}
54+
55+
val actual = stringBuffer.toString.trim
56+
val checkFilePath = file.getPath + File.separator + "expected.check"
57+
val checkContent =
58+
if (new File(checkFilePath).exists)
59+
fromFile(checkFilePath).getLines.mkString("\n").trim
60+
else ""
61+
62+
(file, checkContent, actual)
63+
}
64+
65+
@Test def patmatExhaustivity: Unit = {
66+
val res = Directory(testsDir).list.toList
67+
.filter(f => f.extension == "scala" || f.isDirectory)
68+
.map { f =>
69+
if (f.isDirectory)
70+
compileDir(f.jfile)
71+
else
72+
compileFile(f.jfile)
73+
}
74+
75+
val failed = res.filter { case (_, expected, actual) => expected != actual }
76+
val ignored = Directory(testsDir).list.toList.filter(_.extension == "ignore")
77+
78+
failed.foreach { case (file, expected, actual) =>
79+
println(s"\n----------------- incorrect output for $file --------------\n" +
80+
s"Expected:\n-------\n$expected\n\nActual\n----------\n$actual\n"
81+
)
82+
}
83+
84+
val msg = s"Total: ${res.length + ignored.length}, Failed: ${failed.length}, Ignored: ${ignored.length}"
85+
86+
assert(failed.length == 0, msg)
87+
88+
println(msg)
89+
}
90+
}

tests/patmat/NonAbstractSealed.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
./tests/patmat/NonAbstractSealed.scala:6: warning: match may not be exhaustive.
2+
It would fail on the following input: _: A
3+
(null: A) match {
4+
^
5+
one warning found

tests/patmat/NonAbstractSealed.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sealed class A
2+
class B extends A
3+
class C extends A
4+
5+
object Test {
6+
(null: A) match {
7+
case t: B =>
8+
case t: C =>
9+
}
10+
}

tests/patmat/TwoTrait.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
object Test {
2+
sealed trait A
3+
sealed trait B
4+
5+
abstract sealed class Parent
6+
class Foo extends Parent with A with B
7+
class Bar extends Parent with B with A
8+
9+
(null: A) match {
10+
case _: B =>
11+
}
12+
}

tests/patmat/enum/Day.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public enum Day {
2+
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
3+
THURSDAY, FRIDAY, SATURDAY
4+
}

tests/patmat/enum/expected.check

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
./tests/patmat/enum/patmat-enum.scala:4: warning: match may not be exhaustive.
2+
It would fail on the following input: SATURDAY, FRIDAY, THURSDAY, SUNDAY
3+
day match {
4+
^
5+
./tests/patmat/enum/patmat-enum.scala:15: warning: match may not be exhaustive.
6+
It would fail on the following input: SATURDAY, FRIDAY, THURSDAY
7+
day match {
8+
^
9+
two warnings found

tests/patmat/enum/patmat-enum.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
object Test1 {
2+
val day: Day = ???
3+
4+
day match {
5+
case Day.MONDAY => true
6+
case Day.TUESDAY => true
7+
case Day.WEDNESDAY => true
8+
}
9+
}
10+
11+
object Test2 {
12+
import Day._
13+
val day: Day = ???
14+
15+
day match {
16+
case MONDAY => true
17+
case TUESDAY => true
18+
case WEDNESDAY => true
19+
case SUNDAY => true
20+
}
21+
}

tests/patmat/for.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test {
2+
def foo[A, B](l: List[(A, B)]): List[A] = {
3+
for ((a, b) <- l) yield a
4+
}
5+
}

tests/patmat/gadt.check

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
./tests/patmat/gadt.scala:13: warning: match may not be exhaustive.
2+
It would fail on the following input: IntLit(_)
3+
def foo1b(x: Expr[Int]) = x match {
4+
^
5+
./tests/patmat/gadt.scala:22: warning: match may not be exhaustive.
6+
It would fail on the following input: Or(_, _)
7+
def foo2b(x: Expr[Boolean]) = x match {
8+
^
9+
./tests/patmat/gadt.scala:45: warning: match may not be exhaustive.
10+
It would fail on the following input: BooleanLit(_), IntLit(_)
11+
def foo4b(x: Expr) = x match {
12+
^
13+
./tests/patmat/gadt.scala:55: warning: match may not be exhaustive.
14+
It would fail on the following input: Sum(_, _)
15+
def foo5b[T <: Int](x: Expr[T]) = x match {
16+
^
17+
four warnings found

tests/patmat/gadt.scala

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
object Test {
2+
sealed trait Expr[T]
3+
case class IntLit(i: Int) extends Expr[Int]
4+
case class BooleanLit(b: Boolean) extends Expr[Boolean]
5+
case class Sum(l: Expr[Int], r: Expr[Int]) extends Expr[Int]
6+
case class Or(l: Expr[Boolean], r: Expr[Boolean]) extends Expr[Boolean]
7+
8+
def foo1a(x: Expr[Int]) = x match {
9+
case _: IntLit => true
10+
case _: Sum => true
11+
}
12+
13+
def foo1b(x: Expr[Int]) = x match {
14+
case _: Sum => true
15+
}
16+
17+
def foo2a(x: Expr[Boolean]) = x match {
18+
case _: BooleanLit => true
19+
case _: Or => true
20+
}
21+
22+
def foo2b(x: Expr[Boolean]) = x match {
23+
case _: BooleanLit => true
24+
}
25+
26+
def foo3a(x: Expr[Boolean]) = x match {
27+
case _: BooleanLit => true
28+
case _: Or => true
29+
// case _: Sum => true
30+
}
31+
32+
def foo3b(x: Expr[Int]) = x match {
33+
case _: IntLit => true
34+
case _: Sum => true
35+
// case _: Or => true
36+
}
37+
38+
def foo4a(x: Expr) = x match {
39+
case _: IntLit => true
40+
case _: Sum => true
41+
case _: BooleanLit => true
42+
case _: Or => true
43+
}
44+
45+
def foo4b(x: Expr) = x match {
46+
case _: Sum => true
47+
case _: Or => true
48+
}
49+
50+
def foo5a[T <: Int](x: Expr[T]) = x match {
51+
case _: IntLit => true
52+
case _: Sum => true
53+
}
54+
55+
def foo5b[T <: Int](x: Expr[T]) = x match {
56+
case _: IntLit => true
57+
}
58+
}

tests/patmat/i947.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
./tests/patmat/i947.scala:10: warning: unreachable code
2+
case ys: List[d18383] => false
3+
^
4+
one warning found

tests/patmat/i947.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
object Test {
2+
3+
class c {
4+
5+
private var x: Int = 0
6+
7+
override def equals(other: Any) = other match {
8+
case o: c => x == o.x
9+
case xs: List[c] => false
10+
case ys: List[d18383] => false
11+
case _ => false
12+
}
13+
14+
15+
}
16+
}

tests/patmat/partial-function.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
sealed abstract class TA
2+
sealed abstract class TB extends TA
3+
case object B extends TB
4+
case object B2 extends TB
5+
6+
case class CC(i: Int, tb: TB)
7+
8+
object Test {
9+
def foo: PartialFunction[CC, Unit] = {
10+
case CC(_, B) => ()
11+
}
12+
}

tests/patmat/patmat-adt.check

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
./tests/patmat/patmat-adt.scala:7: warning: match may not be exhaustive.
2+
It would fail on the following input: Bad(Good(_)), Good(Bad(_))
3+
def foo1a(x: Odd) = x match { // warning: Good(_: Bad), Bad(_: Good)
4+
^
5+
./tests/patmat/patmat-adt.scala:19: warning: match may not be exhaustive.
6+
It would fail on the following input: Some(_)
7+
def foo2(x: Option[Int]) = x match { // warning: Some(_: Int)
8+
^
9+
./tests/patmat/patmat-adt.scala:24: warning: match may not be exhaustive.
10+
It would fail on the following input: (None, Some(_)), (_, Some(_))
11+
def foo3a[T](x: Option[T]) = (x, x) match { // warning: (Some(_), Some(_)), (None, Some(_))
12+
^
13+
./tests/patmat/patmat-adt.scala:29: warning: match may not be exhaustive.
14+
It would fail on the following input: (None, None), (Some(_), Some(_))
15+
def foo3b[T](x: Option[T]) = (x, x) match { // warning: (Some(_), Some(_)), (None, None)
16+
^
17+
./tests/patmat/patmat-adt.scala:50: warning: match may not be exhaustive.
18+
It would fail on the following input: LetL(BooleanLit), LetL(IntLit)
19+
def foo5(tree: Tree) : Any = tree match {
20+
^
21+
5 warnings found

tests/patmat/patmat-adt.scala

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
object PatmatADT {
2+
abstract sealed class Odd(x: Odd)
3+
4+
case class Good(x: Odd) extends Odd(x)
5+
case class Bad(x: Odd) extends Odd(x)
6+
7+
def foo1a(x: Odd) = x match { // warning: Good(_: Bad), Bad(_: Good)
8+
case Good(_: Good) => false
9+
case Bad(_: Bad) => false
10+
}
11+
12+
def foo1b(x: Odd) = x match {
13+
case Good(_: Good) => false
14+
case Bad(_: Bad) => false
15+
case Good(_: Bad) => false
16+
case Bad(_: Good) => false
17+
}
18+
19+
def foo2(x: Option[Int]) = x match { // warning: Some(_: Int)
20+
case Some(_: Double) => true
21+
case None => true
22+
}
23+
24+
def foo3a[T](x: Option[T]) = (x, x) match { // warning: (Some(_), Some(_)), (None, Some(_))
25+
case (Some(_), None) => true
26+
case (None, None) => true
27+
}
28+
29+
def foo3b[T](x: Option[T]) = (x, x) match { // warning: (Some(_), Some(_)), (None, None)
30+
case (Some(_), None) => true
31+
case (None, Some(_)) => true
32+
}
33+
34+
sealed trait Base
35+
case class Foo() extends Base
36+
37+
def foo4(x: Base) = x match {
38+
case Foo() =>
39+
}
40+
41+
sealed abstract class CL3Literal
42+
case object IntLit extends CL3Literal
43+
case object CharLit extends CL3Literal
44+
case object BooleanLit extends CL3Literal
45+
46+
47+
sealed abstract class Tree
48+
case class LetL(value: CL3Literal) extends Tree
49+
50+
def foo5(tree: Tree) : Any = tree match {
51+
case LetL(CharLit) =>
52+
}
53+
54+
def foo6[T](l: List[T]): Boolean = l match {
55+
case x::xs => true
56+
case Nil => false
57+
}
58+
}

tests/patmat/patmat-extractor.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
sealed trait Node
2+
case class NodeA(i: Int) extends Node
3+
case class NodeB(b: Boolean) extends Node
4+
case class NodeC(s: String) extends Node
5+
6+
object Node {
7+
def unapply(node: Node): Option[(Node, Node)] = ???
8+
}
9+
10+
// currently scalac can't do anything with following
11+
// it's possible to do better in our case
12+
object Test {
13+
def foo(x: Node): Boolean = x match { // unexhaustive
14+
case Node(NodeA(_), NodeB(_)) => true
15+
case Node(NodeA(4), NodeB(false)) => true // unreachable code
16+
}
17+
}

0 commit comments

Comments
 (0)