Skip to content

Commit 3673834

Browse files
committed
improve reachability for sub-cases
1 parent 1238b33 commit 3673834

File tree

3 files changed

+53
-13
lines changed

3 files changed

+53
-13
lines changed

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ object SpaceEngine {
938938
*/
939939
private def selectorIsBoundVar(selector: Tree, pat: Tree)(using Context): Boolean =
940940
pat match
941-
case Bind(_, _) => selector.symbol == pat.symbol
941+
case b: Bind => selector.symbol == b.symbol
942942
case _ => false
943943

944944
/** Find the index of the parameter in an outer UnApply pattern that directly binds the selector symbol.
@@ -948,8 +948,7 @@ object SpaceEngine {
948948
*
949949
*/
950950
private def selectorParamIndex(selector: Tree, pat: Tree)(using Context): Option[Int] =
951-
pat match
952-
case Bind(_, inner) => selectorParamIndex(selector, inner)
951+
unbind(pat) match
953952
case UnApply(_, _, pats) =>
954953
val idx = pats.indexWhere {
955954
case b: Bind => b.symbol == selector.symbol
@@ -958,30 +957,30 @@ object SpaceEngine {
958957
if idx >= 0 then Some(idx) else None
959958
case _ => None
960959

961-
private def projectSubMatch(pat: Tree, sm: SubMatch)(using Context): Space =
960+
private def projectSubMatch(pat: Tree, sm: SubMatch)(using Context): Option[Space] =
962961
val Match(selector, cases) = sm
963962

964963
val subSpace = Or(cases.map(projectCaseDef))
965964
val selTyp = toUnderlying(selector.tpe)
966965

967966
if selectorIsBoundVar(selector, pat) then
968-
simplify(intersect(project(pat), subSpace))
967+
Some(simplify(intersect(project(pat), subSpace)))
969968
else selectorParamIndex(selector, pat) match
970969
case Some(idx) =>
971970
project(pat) match
972971
case Prod(tp, unappTp, params) =>
973972
val narrowedParam = simplify(intersect(params(idx), subSpace))
974-
simplify(Prod(tp, unappTp, params.updated(idx, narrowedParam)))
975-
case other => other
973+
Some(simplify(Prod(tp, unappTp, params.updated(idx, narrowedParam))))
974+
case other => Some(other)
976975
case None =>
977-
if simplify(minus(project(selTyp), subSpace)) == Empty then project(pat)
978-
else Empty
976+
if simplify(minus(project(selTyp), subSpace)) == Empty then Some(project(pat))
977+
else None
979978

980979
/** Project a single CaseDef to the space it definitely covers */
981980
private def projectCaseDef(c: CaseDef)(using Context): Space =
982981
if !c.guard.isEmpty then Empty
983982
else c.body match
984-
case sm: SubMatch => projectSubMatch(c.pat, sm)
983+
case sm: SubMatch => projectSubMatch(c.pat, sm).getOrElse(Empty)
985984
case _ => project(c.pat)
986985

987986
def checkExhaustivity(m: Match)(using Context): Unit = trace(i"checkExhaustivity($m)") {
@@ -1032,7 +1031,12 @@ object SpaceEngine {
10321031
cases match
10331032
case Nil =>
10341033
case (c @ CaseDef(pat, _, _)) :: rest =>
1035-
val curr = trace(i"project($pat)")(projectPat(pat))
1034+
val (curr, hasContrib) = c.body match
1035+
case sm: SubMatch =>
1036+
projectSubMatch(pat, sm) match
1037+
case Some(smSpace) => (smSpace, true)
1038+
case None => (projectPat(pat), false)
1039+
case _ => (projectPat(pat), true)
10361040
val covered = trace("covered")(simplify(intersect(curr, targetSpace)))
10371041
val prev = trace("prev")(simplify(Or(prevs)))
10381042
if prev == Empty && covered == Empty then // defer until a case is reachable
@@ -1057,8 +1061,8 @@ object SpaceEngine {
10571061
hadNullOnly = true
10581062
report.warning(MatchCaseOnlyNullWarning(), pat.srcPos)
10591063

1060-
// in redundancy check, take guard as false (or potential sub cases as partial) for a sound approximation
1061-
val newPrev = if !c.guard.isEmpty || c.body.isInstanceOf[SubMatch] then prevs else covered :: prevs
1064+
// in redundancy check, take guard as false for a sound approximation
1065+
val newPrev = if hasContrib && c.guard.isEmpty then covered :: prevs else prevs
10621066
recur(rest, newPrev, Nil)
10631067

10641068
recur(m.cases, Nil, Nil)

tests/warn/sub-cases-exhaustivity.check

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,15 @@
2626
| It would fail on pattern case: Wrapper(Red)
2727
|
2828
| longer explanation available when compiling with `-explain`
29+
-- [E030] Match case Unreachable Warning: tests/warn/sub-cases-exhaustivity.scala:53:10 --------------------------------
30+
53 | case AB.A => "unreachable" // warn
31+
| ^^^^
32+
| Unreachable case
33+
-- [E030] Match case Unreachable Warning: tests/warn/sub-cases-exhaustivity.scala:59:14 --------------------------------
34+
59 | case Wrapper(Color.Red) => "unreachable" // warn
35+
| ^^^^^^^^^^^^^^^^^^
36+
| Unreachable case
37+
-- [E030] Match case Unreachable Warning: tests/warn/sub-cases-exhaustivity.scala:68:14 --------------------------------
38+
68 | case Wrapper(_) => "unreachable" // warn
39+
| ^^^^^^^^^^
40+
| Unreachable case

tests/warn/sub-cases-exhaustivity.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,27 @@ def wrappedColorName(w: Wrapper): String =
4242
case Wrapper(c) if c match
4343
case Color.Green => "green"
4444
case Color.Blue => "blue"
45+
46+
enum AB:
47+
case A, B
48+
49+
def testBoundVarReachability(ab: AB) = ab match
50+
case x if x match
51+
case AB.A => "a"
52+
case AB.B => "b"
53+
case AB.A => "unreachable" // warn
54+
55+
def testParamIndexReachability(w: Wrapper) = w match
56+
case Wrapper(c) if c match
57+
case Color.Red => "red"
58+
case Color.Green => "green"
59+
case Wrapper(Color.Red) => "unreachable" // warn
60+
case Wrapper(Color.Blue) => "blue" // not unreachable
61+
62+
def testCombinedReachability(w: Wrapper) = w match
63+
case Wrapper(c) if c match
64+
case Color.Red => "red"
65+
case Color.Green => "green"
66+
case Wrapper(c) if c match
67+
case Color.Blue => "blue"
68+
case Wrapper(_) => "unreachable" // warn

0 commit comments

Comments
 (0)