Skip to content

Commit e26be67

Browse files
Handle exhaustivity and reachability checking of matches with sub cases
1 parent 5e46b01 commit e26be67

File tree

4 files changed

+59
-4
lines changed

4 files changed

+59
-4
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ object Trees {
625625
case class CaseDef[+T <: Untyped] private[ast] (pat: Tree[T], guard: Tree[T], body: Tree[T])(implicit @constructorOnly src: SourceFile)
626626
extends Tree[T] {
627627
type ThisTree[+T <: Untyped] = CaseDef[T]
628+
/** Should this case be considered partial for exhaustivity and unreachability checking */
629+
def maybePartial(using Context): Boolean = !guard.isEmpty || body.isInstanceOf[SubMatch[T]]
628630
}
629631

630632
/** label[tpt]: { expr } */

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ object SpaceEngine {
883883
val targetSpace = trace(i"targetSpace($selTyp)")(project(selTyp))
884884

885885
val patternSpace = Or(m.cases.foldLeft(List.empty[Space]) { (acc, x) =>
886-
val space = if x.guard.isEmpty then trace(i"project(${x.pat})")(project(x.pat)) else Empty
886+
val space = if x.maybePartial then Empty else trace(i"project(${x.pat})")(project(x.pat))
887887
space :: acc
888888
})
889889

@@ -925,7 +925,7 @@ object SpaceEngine {
925925
@tailrec def recur(cases: List[CaseDef], prevs: List[Space], deferred: List[Tree]): Unit =
926926
cases match
927927
case Nil =>
928-
case CaseDef(pat, guard, _) :: rest =>
928+
case (c @ CaseDef(pat, _, _)) :: rest =>
929929
val curr = trace(i"project($pat)")(projectPat(pat))
930930
val covered = trace("covered")(simplify(intersect(curr, targetSpace)))
931931
val prev = trace("prev")(simplify(Or(prevs)))
@@ -951,8 +951,8 @@ object SpaceEngine {
951951
hadNullOnly = true
952952
report.warning(MatchCaseOnlyNullWarning(), pat.srcPos)
953953

954-
// in redundancy check, take guard as false in order to soundly approximate
955-
val newPrev = if guard.isEmpty then covered :: prevs else prevs
954+
// in redundancy check, take guard as false (or potential sub cases as partial) for a sound approximation
955+
val newPrev = if c.maybePartial then prevs else covered :: prevs
956956
recur(rest, newPrev, Nil)
957957

958958
recur(m.cases, Nil, Nil)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- [E029] Pattern Match Exhaustivity Warning: tests/warn/sub-cases-exhaustivity.scala:15:2 -----------------------------
2+
15 | e match // warn: match may not be exhaustive: It would fail on pattern case: E.A(_) | E.B(_)
3+
| ^
4+
| match may not be exhaustive.
5+
|
6+
| It would fail on pattern case: E.A(_), E.B(_)
7+
|
8+
| longer explanation available when compiling with `-explain`
9+
-- [E029] Pattern Match Exhaustivity Warning: tests/warn/sub-cases-exhaustivity.scala:24:2 -----------------------------
10+
24 | e match // warn: match may not be exhaustive: It would fail on pattern case: E.B(_)
11+
| ^
12+
| match may not be exhaustive.
13+
|
14+
| It would fail on pattern case: E.B(_)
15+
|
16+
| longer explanation available when compiling with `-explain`
17+
-- [E030] Match case Unreachable Warning: tests/warn/sub-cases-exhaustivity.scala:32:9 ---------------------------------
18+
32 | case A(_) => 3 // warn: unreacheable
19+
| ^^^^
20+
| Unreachable case
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import scala.language.experimental.matchWithSubCases
2+
3+
enum E:
4+
case A(e: E)
5+
case B(e: E)
6+
case C
7+
8+
def f: E = ???
9+
end E
10+
import E.*
11+
12+
object Test:
13+
val e: E = ???
14+
15+
e match // warn: match may not be exhaustive: It would fail on pattern case: E.A(_) | E.B(_)
16+
case A(e1) with e1.f match
17+
case B(_) => 11
18+
case C => 12
19+
case B(e1) with e1.f match
20+
case C => 21
21+
case A(_) => 22
22+
case C => 3
23+
24+
e match // warn: match may not be exhaustive: It would fail on pattern case: E.B(_)
25+
case A(e1) with e1.f match
26+
case B(_) => 11
27+
case C => 12
28+
case B(e1) with e1.f match
29+
case C => 21
30+
case A(_) => 22
31+
case A(_) => 3 // nowarn: should not be reported as unreachable
32+
case A(_) => 3 // warn: unreacheable
33+
case C => 4

0 commit comments

Comments
 (0)