Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0a7f843

Browse files
Match if sub cases (#23786)
Based on #23736
2 parents 17ba653 + c432c0f commit 0a7f843

24 files changed

+432
-32
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,17 +608,25 @@ object Trees {
608608
extends TermTree[T] {
609609
type ThisTree[+T <: Untyped] = Match[T]
610610
def isInline = false
611+
def isSubMatch = false
611612
}
612613
class InlineMatch[+T <: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]])(implicit @constructorOnly src: SourceFile)
613614
extends Match(selector, cases) {
614615
override def isInline = true
615616
override def toString = s"InlineMatch($selector, $cases)"
616617
}
618+
/** with selector match { cases } */
619+
final class SubMatch[+T <: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]])(implicit @constructorOnly src: SourceFile)
620+
extends Match(selector, cases) {
621+
override def isSubMatch = true
622+
}
617623

618624
/** case pat if guard => body */
619625
case class CaseDef[+T <: Untyped] private[ast] (pat: Tree[T], guard: Tree[T], body: Tree[T])(implicit @constructorOnly src: SourceFile)
620626
extends Tree[T] {
621627
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]]
622630
}
623631

624632
/** label[tpt]: { expr } */
@@ -1180,6 +1188,7 @@ object Trees {
11801188
type Closure = Trees.Closure[T]
11811189
type Match = Trees.Match[T]
11821190
type InlineMatch = Trees.InlineMatch[T]
1191+
type SubMatch = Trees.SubMatch[T]
11831192
type CaseDef = Trees.CaseDef[T]
11841193
type Labeled = Trees.Labeled[T]
11851194
type Return = Trees.Return[T]
@@ -1329,6 +1338,7 @@ object Trees {
13291338
def Match(tree: Tree)(selector: Tree, cases: List[CaseDef])(using Context): Match = tree match {
13301339
case tree: Match if (selector eq tree.selector) && (cases eq tree.cases) => tree
13311340
case tree: InlineMatch => finalize(tree, untpd.InlineMatch(selector, cases)(sourceFile(tree)))
1341+
case tree: SubMatch => finalize(tree, untpd.SubMatch(selector, cases)(sourceFile(tree)))
13321342
case _ => finalize(tree, untpd.Match(selector, cases)(sourceFile(tree)))
13331343
}
13341344
def CaseDef(tree: Tree)(pat: Tree, guard: Tree, body: Tree)(using Context): CaseDef = tree match {

‎compiler/src/dotty/tools/dotc/ast/tpd.scala‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
143143
def InlineMatch(selector: Tree, cases: List[CaseDef])(using Context): Match =
144144
ta.assignType(untpd.InlineMatch(selector, cases), selector, cases)
145145

146+
def SubMatch(selector: Tree, cases: List[CaseDef])(using Context): Match =
147+
ta.assignType(untpd.SubMatch(selector, cases), selector, cases)
148+
146149
def Labeled(bind: Bind, expr: Tree)(using Context): Labeled =
147150
ta.assignType(untpd.Labeled(bind, expr))
148151

‎compiler/src/dotty/tools/dotc/ast/untpd.scala‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
411411
def Closure(env: List[Tree], meth: Tree, tpt: Tree)(implicit src: SourceFile): Closure = new Closure(env, meth, tpt)
412412
def Match(selector: Tree, cases: List[CaseDef])(implicit src: SourceFile): Match = new Match(selector, cases)
413413
def InlineMatch(selector: Tree, cases: List[CaseDef])(implicit src: SourceFile): Match = new InlineMatch(selector, cases)
414+
def SubMatch(selector: Tree, cases: List[CaseDef])(implicit src: SourceFile): SubMatch = new SubMatch(selector, cases)
414415
def CaseDef(pat: Tree, guard: Tree, body: Tree)(implicit src: SourceFile): CaseDef = new CaseDef(pat, guard, body)
415416
def Labeled(bind: Bind, expr: Tree)(implicit src: SourceFile): Labeled = new Labeled(bind, expr)
416417
def Return(expr: Tree, from: Tree)(implicit src: SourceFile): Return = new Return(expr, from)

‎compiler/src/dotty/tools/dotc/config/Feature.scala‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ object Feature:
3737
val modularity = experimental("modularity")
3838
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
3939
val packageObjectValues = experimental("packageObjectValues")
40+
val subCases = experimental("subCases")
4041

4142
def experimentalAutoEnableFeatures(using Context): List[TermName] =
4243
defn.languageExperimentalFeatures

‎compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
590590
if (tree.isInline)
591591
if (selector.isEmpty) writeByte(IMPLICIT)
592592
else { writeByte(INLINE); pickleTree(selector) }
593+
else if tree.isSubMatch then { writeByte(LAZY); pickleTree(selector) }
593594
else pickleTree(selector)
594595
tree.cases.foreach(pickleTree)
595596
}

‎compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,6 +1539,9 @@ class TreeUnpickler(reader: TastyReader,
15391539
readByte()
15401540
InlineMatch(readTree(), readCases(end))
15411541
}
1542+
else if nextByte == LAZY then // similarly to InlineMatch we use an arbitrary Cat.1 tag
1543+
readByte()
1544+
SubMatch(readTree(), readCases(end))
15421545
else Match(readTree(), readCases(end)))
15431546
case RETURN =>
15441547
val from = readSymRef()

‎compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,13 @@ class InlineReducer(inliner: Inliner)(using Context):
394394
case ConstantValue(v: Boolean) => (v, true)
395395
case _ => (false, false)
396396
}
397-
if guardOK then Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard))
398-
else if canReduceGuard then None
399-
else Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard))
397+
if !canReduceGuard then Some((List.empty, EmptyTree, false))
398+
else if !guardOK then None
399+
else cdef.body.subst(from, to) match
400+
case t: SubMatch => // a sub match of an inline match is also inlined
401+
reduceInlineMatch(t.selector, t.selector.tpe, t.cases, typer).map:
402+
(subCaseBindings, rhs) => (caseBindings.map(_.subst(from, to)) ++ subCaseBindings, rhs, true)
403+
case b => Some((caseBindings.map(_.subst(from, to)), b, true))
400404
}
401405
else None
402406
}

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

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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]

‎compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
565565
selTxt ~ keywordStr(" match ") ~ blockText(cases)
566566
}
567567
case CaseDef(pat, guard, body) =>
568-
keywordStr("case ") ~ inPattern(toText(pat)) ~ optText(guard)(keywordStr(" if ") ~ _) ~ " => " ~ caseBlockText(body)
568+
val bodyText = body match
569+
case t: SubMatch => keywordStr(" if ") ~ toText(t)
570+
case t => " => " ~ caseBlockText(t)
571+
keywordStr("case ") ~ inPattern(toText(pat)) ~ optText(guard)(keywordStr(" if ") ~ _) ~ bodyText
569572
case Labeled(bind, expr) =>
570573
changePrec(GlobalPrec) { toText(bind.name) ~ keywordStr("[") ~ toText(bind.symbol.info) ~ keywordStr("]: ") ~ toText(expr) }
571574
case Return(expr, from) =>

‎compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,11 @@ class ExpandSAMs extends MiniPhase:
180180

181181
def isDefinedAtRhs(paramRefss: List[List[Tree]])(using Context) =
182182
val tru = Literal(Constant(true))
183-
def translateCase(cdef: CaseDef) = cpy.CaseDef(cdef)(body = tru)
183+
def translateCase(cdef: CaseDef): CaseDef =
184+
val body1 = cdef.body match
185+
case b: SubMatch => cpy.Match(b)(b.selector, b.cases.map(translateCase))
186+
case _ => tru
187+
cpy.CaseDef(cdef)(body = body1)
184188
val paramRef = paramRefss.head.head
185189
val defaultValue = Literal(Constant(false))
186190
translateMatch(isDefinedAtFn)(paramRef.symbol, pfRHS.cases.map(translateCase), defaultValue)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /