@@ -2589,11 +2589,52 @@ object Parsers {
2589
2589
mkIf(cond, thenp, elsep)
2590
2590
}
2591
2591
2592
+ /* When parsing (what will become) a sub sub match, that is,
2593
+ * when in a guard of case of a match, in a guard of case of a match;
2594
+ * we will eventually reach Scanners.handleNewLine at the end of the sub sub match
2595
+ * with an in.currretRegion of the shape `InCase +: Indented :+ InCase :+ Indented :+ ...`
2596
+ * if we did not do dropInnerCaseRegion.
2597
+ * In effect, a single outdent would be inserted by handleNewLine after the sub sub match.
2598
+ * This causes the remaining cases of the outer match to be included in the intermediate sub match.
2599
+ * For example:
2600
+ * match
2601
+ * case x1 if x1 match
2602
+ * case y if y match
2603
+ * case z => "a"
2604
+ * case x2 => "b"
2605
+ * would become
2606
+ * match
2607
+ * case x1 if x1 match {
2608
+ * case y if y match {
2609
+ * case z => "a"
2610
+ * }
2611
+ * case x2 => "b"
2612
+ * }
2613
+ * This issue is avoided by dropping the `InCase` region when parsing match clause,
2614
+ * since `Indetented :+ Indented :+ ...` now allows handleNewLine to insert two outdents.
2615
+ * Note that this _could_ break previous code which relied on matches within guards
2616
+ * being considered as a separate region without explicit indentation.
2617
+ */
2618
+ private def dropInnerCaseRegion (): Unit =
2619
+ in.currentRegion match
2620
+ case Indented (width, prefix, Scanners .InCase (r)) => in.currentRegion = Indented (width, prefix, r)
2621
+ case Scanners .InCase (r) => in.currentRegion = r
2622
+ case _ =>
2623
+
2592
2624
/** MatchClause ::= `match' `{' CaseClauses `}'
2625
+ * | `match' ExprCaseClause
2593
2626
*/
2594
2627
def matchClause (t : Tree ): Match =
2595
2628
atSpan(startOffset(t), in.skipToken()) {
2596
- Match (t, inBracesOrIndented(caseClauses(() => caseClause())))
2629
+ val cases =
2630
+ if in.featureEnabled(Feature .subCases) then
2631
+ dropInnerCaseRegion()
2632
+ if in.token == CASE
2633
+ then caseClause(exprOnly = true ) :: Nil // single case without new line
2634
+ else inBracesOrIndented(caseClauses(() => caseClause()))
2635
+ else
2636
+ inBracesOrIndented(caseClauses(() => caseClause()))
2637
+ Match (t, cases)
2597
2638
}
2598
2639
2599
2640
/** `match' <<< TypeCaseClauses >>>
@@ -3096,24 +3137,45 @@ object Parsers {
3096
3137
buf.toList
3097
3138
}
3098
3139
3099
- /** CaseClause ::= ‘case’ Pattern [Guard] `=>' Block
3100
- * ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr
3140
+ /** CaseClause ::= ‘case’ Pattern [Guard] (‘if’ InfixExpr MatchClause | `=>' Block)
3141
+ * ExprCaseClause ::= ‘case’ Pattern [Guard] (‘if’ InfixExpr MatchClause | `=>' Expr)
3101
3142
*/
3102
3143
def caseClause (exprOnly : Boolean = false ): CaseDef = atSpan(in.offset) {
3103
3144
val (pat, grd) = inSepRegion(InCase ) {
3104
3145
accept(CASE )
3105
3146
(withinMatchPattern(pattern()), guard())
3106
3147
}
3107
- CaseDef (pat, grd, atSpan(accept(ARROW )) {
3108
- if exprOnly then
3109
- if in.indentSyntax && in.isAfterLineEnd && in.token != INDENT then
3110
- warning(em """ Misleading indentation: this expression forms part of the preceding catch case.
3111
- |If this is intended, it should be indented for clarity.
3112
- |Otherwise, if the handler is intended to be empty, use a multi-line catch with
3113
- |an indented case. """ )
3114
- expr()
3115
- else block()
3116
- })
3148
+ var grd1 = grd // may be reset to EmptyTree (and used as sub match body instead) if there is no leading ARROW
3149
+ val tok = in.token
3150
+
3151
+ extension (self : Tree ) def asSubMatch : Tree = self match
3152
+ case Match (sel, cases) if in.featureEnabled(Feature .subCases) =>
3153
+ if in.isStatSep then in.nextToken() // else may have been consumed by sub sub match
3154
+ SubMatch (sel, cases)
3155
+ case _ =>
3156
+ syntaxErrorOrIncomplete(ExpectedTokenButFound (ARROW , tok))
3157
+ atSpan(self.span)(Block (Nil , EmptyTree ))
3158
+
3159
+ val body = tok match
3160
+ case ARROW => atSpan(in.skipToken()):
3161
+ if exprOnly then
3162
+ if in.indentSyntax && in.isAfterLineEnd && in.token != INDENT then
3163
+ warning(em """ Misleading indentation: this expression forms part of the preceding catch case.
3164
+ |If this is intended, it should be indented for clarity.
3165
+ |Otherwise, if the handler is intended to be empty, use a multi-line catch with
3166
+ |an indented case. """ )
3167
+ expr()
3168
+ else block()
3169
+ case IF => atSpan(in.skipToken()):
3170
+ // a sub match after a guard is parsed the same as one without
3171
+ val t = inSepRegion(InCase )(postfixExpr(Location .InGuard ))
3172
+ t.asSubMatch
3173
+ case other =>
3174
+ val t = grd1.asSubMatch
3175
+ grd1 = EmptyTree
3176
+ t
3177
+
3178
+ CaseDef (pat, grd1, body)
3117
3179
}
3118
3180
3119
3181
/** TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi]
0 commit comments