Skip to content

Commit b1afadb

Browse files
committed
add test set for exhaustivity and redundancy check
1 parent 3c29cdb commit b1afadb

File tree

137 files changed

+3274
-0
lines changed

Some content is hidden

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

137 files changed

+3274
-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", "-Ycheck-all-patmat")
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/aladdin1055/A.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object A {
2+
sealed trait T { def f: Int }
3+
class TT extends T { def f = 0 }
4+
5+
def foo = new T { def f = 1 } // local subclass of sealed trait T
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test {
2+
def foo(t: A.T) = t match {
3+
case a: A.TT => 0
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
./tests/patmat/aladdin1055/Test_1.scala:2: warning: match may not be exhaustive.
2+
It would fail on the following input: (_ : this.<local child>)
3+
def foo(t: A.T) = t match {
4+
^
5+
one warning found

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/exhausting.check

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
./tests/patmat/exhausting.scala:21: warning: match may not be exhaustive.
2+
It would fail on the following input: List(_), List(_, _, _)
3+
def fail1[T](xs: List[T]) = xs match {
4+
^
5+
./tests/patmat/exhausting.scala:27: warning: match may not be exhaustive.
6+
It would fail on the following input: Nil
7+
def fail2[T](xs: List[T]) = xs match {
8+
^
9+
./tests/patmat/exhausting.scala:32: warning: match may not be exhaustive.
10+
It would fail on the following input: List(_, _)
11+
def fail3a(xs: List[Int]) = xs match {
12+
^
13+
./tests/patmat/exhausting.scala:39: warning: match may not be exhaustive.
14+
It would fail on the following input: Bar3
15+
def fail3[T](x: Foo[T]) = x match {
16+
^
17+
./tests/patmat/exhausting.scala:44: warning: match may not be exhaustive.
18+
It would fail on the following input: (Bar2, Bar2)
19+
def fail4[T <: AnyRef](xx: (Foo[T], Foo[T])) = xx match {
20+
^
21+
./tests/patmat/exhausting.scala:53: warning: match may not be exhaustive.
22+
It would fail on the following input: (Bar2, Bar2), (Bar2, Bar1), (Bar1, Bar3), (Bar1, Bar2)
23+
def fail5[T](xx: (Foo[T], Foo[T])) = xx match {
24+
^
25+
6 warnings found

tests/patmat/exhausting.scala

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
object Test {
2+
sealed abstract class Foo[T]
3+
case object Bar1 extends Foo[Int]
4+
case object Bar2 extends Foo[String]
5+
case object Bar3 extends Foo[Any]
6+
7+
def ex1[T](xs: List[T]) = xs match {
8+
case ys: List[_] => "ok"
9+
}
10+
def ex2[T](xx: (Foo[T], Foo[T])) = xx match {
11+
case (Bar1, Bar1) => ()
12+
case (_, Bar1) => ()
13+
case (_, Bar3) => ()
14+
case (_, Bar2) => ()
15+
}
16+
def ex3[T](xx: (Foo[T], Foo[T])) = xx match {
17+
case (_: Foo[_], _: Foo[_]) => ()
18+
}
19+
20+
// fails for: ::(_, Nil), ::(_, ::(_, ::(_, _))), ...
21+
def fail1[T](xs: List[T]) = xs match {
22+
case Nil => "ok"
23+
case x :: y :: Nil => "ok"
24+
}
25+
26+
// fails for: Nil
27+
def fail2[T](xs: List[T]) = xs match {
28+
case _ :: _ => "ok"
29+
}
30+
31+
// fails for: ::(<not in (2, 1)>, _)
32+
def fail3a(xs: List[Int]) = xs match {
33+
case 1 :: _ =>
34+
case 2 :: _ =>
35+
case Nil =>
36+
}
37+
38+
// fails for: Bar3
39+
def fail3[T](x: Foo[T]) = x match {
40+
case Bar1 => "ok"
41+
case Bar2 => "ok"
42+
}
43+
// fails for: (Bar2, Bar2)
44+
def fail4[T <: AnyRef](xx: (Foo[T], Foo[T])) = xx match {
45+
case (Bar1, Bar1) => ()
46+
case (Bar2, Bar3) => ()
47+
case (Bar3, _) => ()
48+
}
49+
// fails for: (Bar1, Bar2)
50+
// fails for: (Bar1, Bar3)
51+
// fails for: (Bar2, Bar1)
52+
// fails for: (Bar2, Bar2)
53+
def fail5[T](xx: (Foo[T], Foo[T])) = xx match {
54+
case (Bar1, Bar1) => ()
55+
case (Bar2, Bar3) => ()
56+
case (Bar3, _) => ()
57+
}
58+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// tests exhaustivity doesn't give warnings (due to its heuristic rewrites kicking in or it backing off)
2+
object Test {
3+
// List() => Nil
4+
List(1) match {
5+
case List() =>
6+
case x :: xs =>
7+
}
8+
9+
// we don't look into guards
10+
val turnOffChecks = true
11+
List(1) match {
12+
case _ if turnOffChecks =>
13+
}
14+
15+
// we back off when there are any user-defined extractors
16+
// in fact this is exhaustive, but we pretend we don't know since List's unapplySeq is not special to the compiler
17+
// to compensate our ignorance, we back off
18+
// well, in truth, we do rewrite List() to Nil, but otherwise we do nothing
19+
// the full rewrite List(a, b) to a :: b :: Nil, for example is planned (but not sure it's a good idea)
20+
List(true, false) match {
21+
case List(_, _, _:_*) =>
22+
case List(node, _:_*) =>
23+
case Nil =>
24+
}
25+
26+
}

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/gadt2.scala.ignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
sealed trait Nat[+T]
2+
case class Zero() extends Nat[Nothing]
3+
case class Succ[T]() extends Nat[T]
4+
5+
sealed trait Vect[+N <: Nat[_], +T]
6+
case class VN[T]() extends Vect[Zero, T]
7+
case class VC[T, N <: Nat[_]](x: T, xs: Vect[N, T]) extends Vect[Succ[N], T]
8+
9+
object Test {
10+
def foo[N <: Nat[_], A, B](v1: Vect[N, A], v2: Vect[N, B]) = (v1, v2) match {
11+
case (VN(), VN()) => 1
12+
case (VC(x, xs), VC(y, ys)) => 2
13+
}
14+
}

0 commit comments

Comments
 (0)