Skip to content

Commit b643009

Browse files
Replace sub match introducer from with to if
1 parent f7e0512 commit b643009

File tree

9 files changed

+94
-45
lines changed

9 files changed

+94
-45
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,22 +2585,54 @@ object Parsers {
25852585
mkIf(cond, thenp, elsep)
25862586
}
25872587

2588+
/* When parsing (what will become) a sub sub match, that is,
2589+
* when in a guard of case of a match, in a guard of case of a match;
2590+
* we will eventually reach Scanners.handleNewLine at the end of the sub sub match
2591+
* with an in.currretRegion of the shape `InCase +: Indented :+ InCase :+ Indented :+ ...`
2592+
* if we did not do dropInnerCaseRegion.
2593+
* In effect, a single outdent would be inserted by handleNewLine after the sub sub match.
2594+
* This causes the remaining cases of the outer match to be included in the intermediate sub match.
2595+
* For example:
2596+
* match
2597+
* case x1 if x1 match
2598+
* case y if y match
2599+
* case z => "a"
2600+
* case x2 => "b"
2601+
* would become
2602+
* match
2603+
* case x1 if x1 match {
2604+
* case y if y match {
2605+
* case z => "a"
2606+
* }
2607+
* case x2 => "b"
2608+
* }
2609+
* This issue is avoided by dropping the `InCase` region when parsing match clause,
2610+
* since `Indetented :+ Indented :+ ...` now allows handleNewLine to insert two outdents.
2611+
* Note that this _could_ break previous code which relied on matches within guards
2612+
* being considered as a separate region without explicit indentation.
2613+
*/
2614+
private def dropInnerCaseRegion(): Unit =
2615+
in.currentRegion match
2616+
case Indented(width, prefix, Scanners.InCase(r)) => in.currentRegion = Indented(width, prefix, r)
2617+
case Scanners.InCase(r) => in.currentRegion = r
2618+
case _ =>
2619+
25882620
/** MatchClause ::= `match' `{' CaseClauses `}'
2621+
* | `match' ExprCaseClause
25892622
*/
25902623
def matchClause(t: Tree): Match =
25912624
atSpan(startOffset(t), in.skipToken()) {
2592-
Match(t, inBracesOrIndented(caseClauses(() => caseClause())))
2625+
val cases =
2626+
if in.featureEnabled(Feature.subCases) then
2627+
dropInnerCaseRegion()
2628+
if in.token == CASE
2629+
then caseClause(exprOnly = true) :: Nil // single case without new line
2630+
else inBracesOrIndented(caseClauses(() => caseClause()))
2631+
else
2632+
inBracesOrIndented(caseClauses(() => caseClause()))
2633+
Match(t, cases)
25932634
}
25942635

2595-
/** SubMatchClause ::= `match' `{' CaseClauses `}'
2596-
*/
2597-
def subMatchClause(t: Tree): SubMatch = atSpan(startOffset(t), accept(MATCH)):
2598-
val cases =
2599-
if in.token == CASE
2600-
then caseClause(exprOnly = true) :: Nil // single sub case without new line
2601-
else inBracesOrIndented(caseClauses(() => caseClause()))
2602-
SubMatch(t, cases)
2603-
26042636
/** `match' <<< TypeCaseClauses >>>
26052637
*/
26062638
def matchType(t: Tree): MatchTypeTree =
@@ -3109,12 +3141,19 @@ object Parsers {
31093141
accept(CASE)
31103142
(withinMatchPattern(pattern()), guard())
31113143
}
3112-
val body =
3113-
if in.token == WITH && in.featureEnabled(Feature.subCases) then atSpan(in.skipToken()):
3114-
val t = subMatchClause(simpleExpr(Location.ElseWhere))
3144+
var grd1 = grd // may be reset to EmptyTree (and used as sub match body instead) if there is no leading ARROW
3145+
val tok = in.token
3146+
3147+
extension (self: Tree) def asSubMatch: Tree = self match
3148+
case Match(sel, cases) if in.featureEnabled(Feature.subCases) =>
31153149
if in.isStatSep then in.nextToken() // else may have been consumed by sub sub match
3116-
t
3117-
else atSpan(accept(ARROW)):
3150+
SubMatch(sel, cases)
3151+
case _ =>
3152+
syntaxErrorOrIncomplete(ExpectedTokenButFound(ARROW, tok))
3153+
EmptyTree
3154+
3155+
val body = tok match
3156+
case ARROW => atSpan(in.skipToken()):
31183157
if exprOnly then
31193158
if in.indentSyntax && in.isAfterLineEnd && in.token != INDENT then
31203159
warning(em"""Misleading indentation: this expression forms part of the preceding catch case.
@@ -3123,7 +3162,16 @@ object Parsers {
31233162
|an indented case.""")
31243163
expr()
31253164
else block()
3126-
CaseDef(pat, grd, body)
3165+
case IF => atSpan(in.skipToken()):
3166+
// a sub match after a guard is parsed the same as one without
3167+
val t = inSepRegion(InCase)(postfixExpr(Location.InGuard))
3168+
t.asSubMatch
3169+
case other =>
3170+
val t = grd1.asSubMatch
3171+
grd1 = EmptyTree
3172+
t
3173+
3174+
CaseDef(pat, grd1, body)
31273175
}
31283176

31293177
/** TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi]

tests/neg/parser-stability-20.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ object x0 {
22
def unapply= Array
33
x0 match
44
x0 // error
5-
case x0( // error
6-
// error
5+
case x0(

tests/pos/inline-match-sub-cases.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ object Test:
55
// using transparent to test whether test whether reduced as expected
66
transparent inline def foo(i: Int, j: Int): String =
77
inline i match
8-
case 0 with j match
8+
case 0 if j match
99
case 1 => "01"
1010
case 2 => "02"
11-
case 1 with j match
11+
case 1 if j match
1212
case 1 => "11"
1313
case 2 => "12"
1414
case _ => "3"
@@ -20,7 +20,7 @@ object Test:
2020

2121
transparent inline def bar(x: Option[Any]): String =
2222
inline x match
23-
case Some(y: Int) with y match
23+
case Some(y: Int) if y match
2424
case 1 => "a"
2525
case 2 => "b"
2626
case Some(z: String) => "c"

tests/pos/match-single-sub-case.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import scala.language.experimental.subCases
22

3-
// single sub case can be one the same line as outer case
3+
// single sub case can be on the same line as outer case
44
object Test:
55
val x: Option[Option[Int]] = ???
66
x match
7-
case Some(x2) with x2 match case Some(x3) => "aa"
8-
case Some(x2) if false with x2 match case Some(x3) if true => "aa"
9-
case Some(x2) with x2 match case Some(x3) with x2 match case Some(x3) => "bb"
10-
case Some(y2) with y2 match
11-
case Some(y3) with y3 match
7+
case Some(x2) if x2 match case Some(x3) => "aa"
8+
case Some(x2) if false if x2 match case Some(x3) if true => "aa"
9+
case Some(x2) if x2 match case Some(x3) if x2 match case Some(x3) => "bb"
10+
case Some(y2) if y2 match
11+
case Some(y3) if y3 match
1212
case 1 => "a"
1313
case 2 => "b"
14-
case Some(x2) with x2 match case Some(x3) with x3 match
14+
case Some(x2) if x2 match case Some(x3) if x3 match
1515
case 1 => "a"
1616
case 2 => "b"
1717
case None => "d"

tests/pos/match-sub-sub-cases.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ import scala.language.experimental.subCases
33
object Test:
44
val x: Option[Option[Int]] = ???
55
x match
6-
case Some(x2) with x2 match
7-
case Some(x3) with x3 match
6+
case Some(x2) if true if x2 match
7+
case Some(x3) if false if x3 match
88
case 1 => "a"
9-
case 2 => "b"
9+
case x if x % 2 == 0 if x match
10+
case 4 => "b"
11+
case 6 => "b"
1012
case None => "d"
1113

1214
x match {
13-
case Some(x2) with x2 match {
14-
case Some (x3) with x3 match {
15+
case Some(x2) if x2 match {
16+
case Some (x3) if x3 match {
1517
case 1 => "a"
1618
case 2 => "b"
1719
}

tests/run/catch-sub-cases.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import E.*
1010

1111
def test(op: => Nothing): String =
1212
try op catch
13-
case A(x: Int) if true with x match
13+
case A(x: Int) if true if x match
1414
case 1 => "A(1)"
1515
case 2 => "A(2)"
16-
case B(x: String) with x match
16+
case B(x: String) if x match
1717
case "a" => "B(a)"
1818
case "b" => "B(b)"
1919
case _ => "other"

tests/run/match-sub-cases.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ import E.*
1717
@main def Test =
1818

1919
def test(e: E): Int = e match
20-
case A(B(e1)) if true with e1.f match
21-
case Some(x) with x match
20+
case A(B(e1)) if true if e1.f match
21+
case Some(x) if x match
2222
case A(_) => 11
2323
case C => 12
24-
case B(A(e1)) with e1.f match
25-
case Some(C) => 21
24+
case B(A(e1)) if e1.f match
25+
case Some(C) if false || true => 21
2626
case None => 22
2727
case _ => 3
2828
end test

tests/run/pf-sub-cases.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import scala.language.experimental.subCases
22

33
val pf: PartialFunction[Option[Option[Int]], String] =
4-
case Some(x2) with x2 match
5-
case Some(x3) with x3 match
4+
case Some(x2) if x2 match
5+
case Some(x3) if x3 match
66
case 1 => "a"
77
case 2 => "b"
88
case Some(None) => "c"

tests/warn/sub-cases-exhaustivity.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ object Test:
1313
val e: E = ???
1414

1515
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
16+
case A(e1) if e1.f match
1717
case B(_) => 11
1818
case C => 12
19-
case B(e1) with e1.f match
19+
case B(e1) if e1.f match
2020
case C => 21
2121
case A(_) => 22
2222
case C => 3
2323

2424
e match // warn: match may not be exhaustive: It would fail on pattern case: E.B(_)
25-
case A(e1) with e1.f match
25+
case A(e1) if e1.f match
2626
case B(_) => 11
2727
case C => 12
28-
case B(e1) with e1.f match
28+
case B(e1) if e1.f match
2929
case C => 21
3030
case A(_) => 22
3131
case A(_) => 3 // nowarn: should not be reported as unreachable

0 commit comments

Comments
 (0)