@@ -2589,11 +2589,52 @@ object Parsers {
25892589 mkIf(cond, thenp, elsep)
25902590 }
25912591
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+
25922624 /** MatchClause ::= `match' `{' CaseClauses `}'
2625+ * | `match' ExprCaseClause
25932626 */
25942627 def matchClause (t : Tree ): Match =
25952628 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)
25972638 }
25982639
25992640 /** `match' <<< TypeCaseClauses >>>
@@ -3096,24 +3137,45 @@ object Parsers {
30963137 buf.toList
30973138 }
30983139
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)
31013142 */
31023143 def caseClause (exprOnly : Boolean = false ): CaseDef = atSpan(in.offset) {
31033144 val (pat, grd) = inSepRegion(InCase ) {
31043145 accept(CASE )
31053146 (withinMatchPattern(pattern()), guard())
31063147 }
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)
31173179 }
31183180
31193181 /** TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi]
0 commit comments