summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@epfl.ch>2012-07-16 04:56:04 -0700
committerAdriaan Moors <adriaan.moors@epfl.ch>2012-07-16 04:56:04 -0700
commitd6e17dcc9af46d7f92b26063366f6c8dedae58e9 (patch)
tree2e35ae76b566bf6e2e24cebc3261f7cd1a8fdcbd /src
parent6d0d5054faab2c0d2d4c112def6d58f7965ad69b (diff)
parentef2bf411427624b77c4c48a7303176f1664321a2 (diff)
downloadscala-d6e17dcc9af46d7f92b26063366f6c8dedae58e9.tar.gz
scala-d6e17dcc9af46d7f92b26063366f6c8dedae58e9.tar.bz2
scala-d6e17dcc9af46d7f92b26063366f6c8dedae58e9.zip
Merge pull request #876 from adriaanm/ticket-6011b
SI-6011 switches: unreachability, guard-free form
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala375
-rw-r--r--src/reflect/scala/reflect/internal/TreeInfo.scala3
2 files changed, 236 insertions, 142 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala
index b1e68e2757..da45b9bde3 100644
--- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala
@@ -27,14 +27,13 @@ import reflect.internal.util.Statistics
* Cases are combined into a pattern match using the `orElse` combinator (the implicit failure case is expressed using the monad's `zero`).
*
* TODO:
- * - use TypeTags for type testing
* - DCE (on irrefutable patterns)
* - update spec and double check it's implemented correctly (see TODO's)
*
* (longer-term) TODO:
* - user-defined unapplyProd
* - recover GADT typing by locally inserting implicit witnesses to type equalities derived from the current case, and considering these witnesses during subtyping (?)
- * - recover exhaustivity and unreachability checking using a variation on the type-safe builder pattern
+ * - recover exhaustivity/unreachability of user-defined extractors by partitioning the types they match on using an HList or similar type-level structure
*/
trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL { // self: Analyzer =>
import Statistics._
@@ -1248,6 +1247,24 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
def typesConform(tp: Type, pt: Type) = ((tp eq pt) || (tp <:< pt))
+ // we use subtyping as a model for implication between instanceof tests
+ // i.e., when S <:< T we assume x.isInstanceOf[S] implies x.isInstanceOf[T]
+ // unfortunately this is not true in general:
+ // SI-6022 expects instanceOfTpImplies(ProductClass.tpe, AnyRefClass.tpe)
+ def instanceOfTpImplies(tp: Type, tpImplied: Type) = {
+ val tpValue = tp.typeSymbol.isPrimitiveValueClass
+
+ // pretend we're comparing to Any when we're actually comparing to AnyVal or AnyRef
+ // (and the subtype is respectively a value type or not a value type)
+ // this allows us to reuse subtyping as a model for implication between instanceOf tests
+ // the latter don't see a difference between AnyRef, Object or Any when comparing non-value types -- SI-6022
+ val tpImpliedNormalizedToAny =
+ if (tpImplied =:= (if (tpValue) AnyValClass.tpe else AnyRefClass.tpe)) AnyClass.tpe
+ else tpImplied
+
+ tp <:< tpImpliedNormalizedToAny
+ }
+
abstract class CommonCodegen extends AbsCodegen { import CODE._
def fun(arg: Symbol, body: Tree): Tree = Function(List(ValDef(arg)), body)
def genTypeApply(tfun: Tree, args: Type*): Tree = if(args contains NoType) tfun else TypeApply(tfun, args.toList map TypeTree)
@@ -2173,25 +2190,6 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
def isAny = wideTp.typeSymbol == AnyClass
- // we use subtyping as a model for implication between instanceof tests
- // i.e., when S <:< T we assume x.isInstanceOf[S] implies x.isInstanceOf[T]
- // unfortunately this is not true in general:
- // SI-6022 expects instanceOfTpImplies(ProductClass.tpe, AnyRefClass.tpe)
- private def instanceOfTpImplies(tp: Type, tpImplied: Type) = {
- val tpValue = tp.typeSymbol.isPrimitiveValueClass
-
- // pretend we're comparing to Any when we're actually comparing to AnyVal or AnyRef
- // (and the subtype is respectively a value type or not a value type)
- // this allows us to reuse subtyping as a model for implication between instanceOf tests
- // the latter don't see a difference between AnyRef, Object or Any when comparing non-value types -- SI-6022
- val tpImpliedNormalizedToAny =
- if ((tpValue && tpImplied =:= AnyValClass.tpe) ||
- (!tpValue && tpImplied =:= AnyRefClass.tpe)) AnyClass.tpe
- else tpImplied
-
- tp <:< tpImpliedNormalizedToAny
- }
-
final def implies(other: Const): Boolean = {
val r = (this, other) match {
case (_: ValueConst, _: ValueConst) => this == other // hashconsed
@@ -2942,6 +2940,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
//// SWITCHES -- TODO: operate on Tests rather than TreeMakers
trait SwitchEmission extends TreeMakers with OptimizedMatchMonadInterface { self: CodegenCore =>
+ import treeInfo.isGuardedCase
+
abstract class SwitchMaker {
abstract class SwitchableTreeMakerExtractor { def unapply(x: TreeMaker): Option[Tree] }
val SwitchableTreeMaker: SwitchableTreeMakerExtractor
@@ -2978,91 +2978,170 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
/** Collapse guarded cases that switch on the same constant (the last case may be unguarded).
*
- * `{case C if(G_i) => B_i | case C'_j if G'_j => B'_j}*` is rewritten to
- * `case C => {if(G_i) B_i}*` ++ rewrite({case C'_j if G'_j => B'_j}*)
+ * Cases with patterns A and B switch on the same constant iff for all values x that match A also match B and vice versa.
+ * (This roughly corresponds to equality on trees modulo alpha renaming and reordering of alternatives.)
+ *
+ * The rewrite only applies if some of the cases are guarded (this must be checked before invoking this method).
+ *
+ * The rewrite goes through the switch top-down and merges each case with the subsequent cases it is implied by
+ * (i.e. it matches if they match, not taking guards into account)
+ *
+ * If there are no unreachable cases, all cases can be uniquely assigned to a partition of such 'overlapping' cases,
+ * save for the default case (thus we jump to it rather than copying it several times).
+ * (The cases in a partition are implied by the principal element of the partition.)
*
+ * The overlapping cases are merged into one case with their guards pushed into the body as follows
+ * (with P the principal element of the overlapping patterns Pi):
+ *
+ * `{case Pi if(G_i) => B_i }*` is rewritten to `case P => {if(G_i) B_i}*`
+ *
+ * The rewrite fails (and returns Nil) when:
+ * (1) there is a subsequence of overlapping cases that has an unguarded case in the middle;
+ * only the last case of each subsequence of overlapping cases may be unguarded (this is implied by unreachability)
+ *
+ * (2) there are overlapping cases that differ (tested by `caseImpliedBy`)
+ * cases with patterns A and B are overlapping if for SOME value x, A matches x implies B matches y OR vice versa <-- note the difference with case equality defined above
+ * for example `case 'a' | 'b' =>` and `case 'b' =>` are different and overlapping (overlapping and equality disregard guards)
+ *
+ * The second component of the returned tuple indicates whether we'll need to emit a labeldef to jump to the default case.
*/
- private def collapseGuardedCases(cases: List[CaseDef]) = {
- // requires(switchesOnSameConst.forall(caseChecksSameConst(switchesOnSameConst.head)))
- def collapse(switchesOnSameConst: List[CaseDef]): List[CaseDef] =
- if (switchesOnSameConst.tail.isEmpty && (switchesOnSameConst.head.guard == EmptyTree)) switchesOnSameConst
- else {
- val commonPattern = switchesOnSameConst.head.pat
-
- // jump to default case (either the user-supplied one or the synthetic one)
- // unless we're collapsing the default case, then re-use the same body as the synthetic catchall (throwing a matcherror, rethrowing the exception)
- val jumpToDefault: Tree =
- if (!canJump || isDefault(CaseDef(commonPattern, EmptyTree, EmptyTree))) defaultBody
- else Apply(Ident(defaultLabel), Nil)
-
- val guardedBody = switchesOnSameConst.foldRight(jumpToDefault){
- // the last case may be un-guarded (we know it's the last one since fold's accum == jumpToDefault)
- // --> replace jumpToDefault by the un-guarded case's body
- case (CaseDef(_, EmptyTree, b), `jumpToDefault`) => b
- case (CaseDef(_, g, b), els) if g != EmptyTree => If(g, b, els)
- // error: the un-guarded case did not come last
- case _ =>
- return switchesOnSameConst
- }
+ private def collapseGuardedCases(cases: List[CaseDef]): (List[CaseDef], Boolean) = {
+ // requires(same.forall(caseEquals(same.head)))
+ // requires(same.nonEmpty, same)
+ def collapse(same: List[CaseDef], isDefault: Boolean): CaseDef = {
+ val commonPattern = same.head.pat
+ // jump to default case (either the user-supplied one or the synthetic one)
+ // unless we're collapsing the default case: then we re-use the same body as the synthetic catchall (throwing a matcherror, rethrowing the exception)
+ val jumpToDefault: Tree =
+ if (isDefault || !canJump) defaultBody
+ else Apply(Ident(defaultLabel), Nil)
+
+ val guardedBody = same.foldRight(jumpToDefault){
+ // the last case may be un-guarded (we know it's the last one since fold's accum == jumpToDefault)
+ // --> replace jumpToDefault by the un-guarded case's body
+ case (CaseDef(_, EmptyTree, b), `jumpToDefault`) => b
+ case (cd@CaseDef(_, g, b), els) if isGuardedCase(cd) => If(g, b, els)
+ }
- // if the cases that we're going to collapse bind variables,
- // must replace them by the single binder introduced by the collapsed case
- val binders = switchesOnSameConst.collect{case CaseDef(x@Bind(_, _), _, _) if x.symbol != NoSymbol => x.symbol}
- val (pat, guardedBodySubst) =
- if (binders.isEmpty) (commonPattern, guardedBody)
- else {
- // create a single fresh binder to subsume the old binders (and their types)
- // TODO: I don't think the binder's types can actually be different (due to checks in caseChecksSameConst)
- // if they do somehow manage to diverge, the lub might not be precise enough and we could get a type error
- val binder = freshSym(binders.head.pos, lub(binders.map(_.tpe)))
-
- // the patterns in switchesOnSameConst are equal (according to caseChecksSameConst) and modulo variable-binding
- // we can thus safely pick the first one arbitrarily, provided we correct binding
- val origPatWithoutBind = commonPattern match {
- case Bind(b, orig) => orig
- case o => o
- }
- // need to replace `defaultSym` as well -- it's used in `defaultBody` (see `jumpToDefault` above)
- val unifiedBody = guardedBody.substituteSymbols(defaultSym :: binders, binder :: binders.map(_ => binder))
- (Bind(binder, origPatWithoutBind), unifiedBody)
+ // if the cases that we're going to collapse bind variables,
+ // must replace them by the single binder introduced by the collapsed case
+ val binders = same.collect{case CaseDef(x@Bind(_, _), _, _) if x.symbol != NoSymbol => x.symbol}
+ val (pat, guardedBodySubst) =
+ if (binders.isEmpty) (commonPattern, guardedBody)
+ else {
+ // create a single fresh binder to subsume the old binders (and their types)
+ // TODO: I don't think the binder's types can actually be different (due to checks in caseEquals)
+ // if they do somehow manage to diverge, the lub might not be precise enough and we could get a type error
+ // TODO: reuse name exactly if there's only one binder in binders
+ val binder = freshSym(binders.head.pos, lub(binders.map(_.tpe)), binders.head.name.toString)
+
+ // the patterns in same are equal (according to caseEquals)
+ // we can thus safely pick the first one arbitrarily, provided we correct binding
+ val origPatWithoutBind = commonPattern match {
+ case Bind(b, orig) => orig
+ case o => o
}
+ // need to replace `defaultSym` as well -- it's used in `defaultBody` (see `jumpToDefault` above)
+ val unifiedBody = guardedBody.substituteSymbols(defaultSym :: binders, binder :: binders.map(_ => binder))
+ (Bind(binder, origPatWithoutBind), unifiedBody)
+ }
- List(CaseDef(pat, EmptyTree, guardedBodySubst))
- }
+ atPos(commonPattern.pos)(CaseDef(pat, EmptyTree, guardedBodySubst))
+ }
- @annotation.tailrec def partitionAndCollapse(cases: List[CaseDef], accum: List[CaseDef] = Nil): List[CaseDef] =
- if (cases.isEmpty) accum
- else {
- val (same, others) = cases.tail partition (caseChecksSameConst(cases.head))
- partitionAndCollapse(others, accum ++ collapse(cases.head :: same))
+ // requires cases.exists(isGuardedCase) (otherwise the rewrite is pointless)
+ var remainingCases = cases
+ val collapsed = collection.mutable.ListBuffer.empty[CaseDef]
+
+ // when some of collapsed cases (except for the default case itself) did not include an un-guarded case
+ // we'll need to emit a labeldef for the default case
+ var needDefault = false
+
+ while (remainingCases.nonEmpty) {
+ val currCase = remainingCases.head
+ val currIsDefault = isDefault(CaseDef(currCase.pat, EmptyTree, EmptyTree))
+ val (impliesCurr, others) =
+ // the default case is implied by all cases, no need to partition (and remainingCases better all be default cases as well)
+ if (currIsDefault) (remainingCases.tail, Nil)
+ else remainingCases.tail partition (caseImplies(currCase))
+
+ val unguardedComesLastOrAbsent =
+ (!isGuardedCase(currCase) && impliesCurr.isEmpty) || { val LastImpliesCurr = impliesCurr.length - 1
+ impliesCurr.indexWhere(oc => !isGuardedCase(oc)) match {
+ // if all cases are guarded we will have to jump to the default case in the final else
+ // (except if we're collapsing the default case itself)
+ case -1 =>
+ if (!currIsDefault) needDefault = true
+ true
+
+ // last case is not guarded, no need to jump to the default here
+ // note: must come after case -1 => (since LastImpliesCurr may be -1)
+ case LastImpliesCurr => true
+
+ case _ => false
+ }}
+
+ if (unguardedComesLastOrAbsent /*(1)*/ && impliesCurr.forall(caseEquals(currCase)) /*(2)*/) {
+ collapsed += (
+ if (impliesCurr.isEmpty && !isGuardedCase(currCase)) currCase
+ else collapse(currCase :: impliesCurr, currIsDefault)
+ )
+
+ remainingCases = others
+ } else { // fail
+ collapsed.clear()
+ remainingCases = Nil
}
+ }
- // common case: no rewrite needed when there are no guards
- if (cases.forall(c => c.guard == EmptyTree)) cases
- else partitionAndCollapse(cases)
+ (collapsed.toList, needDefault)
}
- private def caseChecksSameConst(x: CaseDef)(y: CaseDef) = (x, y) match {
+ private def caseEquals(x: CaseDef)(y: CaseDef) = patternEquals(x.pat)(y.pat)
+ private def patternEquals(x: Tree)(y: Tree): Boolean = (x, y) match {
+ case (Alternative(xs), Alternative(ys)) =>
+ xs.forall(x => ys.exists(patternEquals(x))) &&
+ ys.forall(y => xs.exists(patternEquals(y)))
+ case (Alternative(pats), _) => pats.forall(p => patternEquals(p)(y))
+ case (_, Alternative(pats)) => pats.forall(q => patternEquals(x)(q))
// regular switch
- case (CaseDef(Literal(Constant(cx)), _, _), CaseDef(Literal(Constant(cy)), _, _)) => cx == cy
- case (CaseDef(Ident(nme.WILDCARD), _, _), CaseDef(Ident(nme.WILDCARD), _, _)) => true
+ case (Literal(Constant(cx)), Literal(Constant(cy))) => cx == cy
+ case (Ident(nme.WILDCARD), Ident(nme.WILDCARD)) => true
// type-switch for catch
- case (CaseDef(Bind(_, Typed(Ident(nme.WILDCARD), tpX)), _, _), CaseDef(Bind(_, Typed(Ident(nme.WILDCARD), tpY)), _, _)) => tpX.tpe =:= tpY.tpe
+ case (Bind(_, Typed(Ident(nme.WILDCARD), tpX)), Bind(_, Typed(Ident(nme.WILDCARD), tpY))) => tpX.tpe =:= tpY.tpe
case _ => false
}
- private def checkNoGuards(cs: List[CaseDef]) =
- if (cs.exists{case CaseDef(_, g, _) => g != EmptyTree case _ => false}) None
- else Some(cs)
+ // if y matches then x matches for sure (thus, if x comes before y, y is unreachable)
+ private def caseImplies(x: CaseDef)(y: CaseDef) = patternImplies(x.pat)(y.pat)
+ private def patternImplies(x: Tree)(y: Tree): Boolean = (x, y) match {
+ // since alternatives are flattened, must treat them as separate cases
+ case (Alternative(pats), _) => pats.exists(p => patternImplies(p)(y))
+ case (_, Alternative(pats)) => pats.exists(q => patternImplies(x)(q))
+ // regular switch
+ case (Literal(Constant(cx)), Literal(Constant(cy))) => cx == cy
+ case (Ident(nme.WILDCARD), _) => true
+ // type-switch for catch
+ case (Bind(_, Typed(Ident(nme.WILDCARD), tpX)),
+ Bind(_, Typed(Ident(nme.WILDCARD), tpY))) => instanceOfTpImplies(tpY.tpe, tpX.tpe)
+ case _ => false
+ }
+
+ private def noGuards(cs: List[CaseDef]): Boolean = !cs.exists(isGuardedCase)
- // requires(cs.forall(_.guard == EmptyTree))
+ // must do this before removing guards from cases and collapsing (SI-6011, SI-6048)
private def unreachableCase(cs: List[CaseDef]): Option[CaseDef] = {
var cases = cs
var unreachable: Option[CaseDef] = None
while (cases.nonEmpty && unreachable.isEmpty) {
- if (isDefault(cases.head) && cases.tail.nonEmpty) unreachable = Some(cases.tail.head)
- else unreachable = cases.tail.find(caseChecksSameConst(cases.head))
+ val currCase = cases.head
+ if (isDefault(currCase) && cases.tail.nonEmpty) // subsumed by the `else if` that follows, but faster
+ unreachable = Some(cases.tail.head)
+ else if (!isGuardedCase(currCase) || currCase.guard.tpe =:= ConstantType(Constant(true)))
+ unreachable = cases.tail.find(caseImplies(currCase))
+ else if (currCase.guard.tpe =:= ConstantType(Constant(false)))
+ unreachable = Some(currCase)
cases = cases.tail
}
@@ -3071,68 +3150,80 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
}
// empty list ==> failure
- def apply(cases: List[(Symbol, List[TreeMaker])], pt: Type): List[CaseDef] = {
- val caseDefs = cases map { case (scrutSym, makers) =>
- makers match {
- // default case
- case GuardAndBodyTreeMakers(guard, body) =>
- Some(defaultCase(scrutSym, guard, body))
- // constant (or typetest for typeSwitch)
- case SwitchableTreeMaker(pattern) :: GuardAndBodyTreeMakers(guard, body) =>
- Some(CaseDef(pattern, guard, body))
- // alternatives
- case AlternativesTreeMaker(_, altss, _) :: GuardAndBodyTreeMakers(guard, body) if alternativesSupported =>
- val switchableAlts = altss map {
- case SwitchableTreeMaker(pattern) :: Nil =>
- Some(pattern)
- case _ =>
- None
- }
+ def apply(cases: List[(Symbol, List[TreeMaker])], pt: Type): List[CaseDef] =
+ // generate if-then-else for 1 case switch (avoids verify error... can't imagine a one-case switch being faster than if-then-else anyway)
+ if (cases.isEmpty || cases.tail.isEmpty) Nil
+ else {
+ val caseDefs = cases map { case (scrutSym, makers) =>
+ makers match {
+ // default case
+ case GuardAndBodyTreeMakers(guard, body) =>
+ Some(defaultCase(scrutSym, guard, body))
+ // constant (or typetest for typeSwitch)
+ case SwitchableTreeMaker(pattern) :: GuardAndBodyTreeMakers(guard, body) =>
+ Some(CaseDef(pattern, guard, body))
+ // alternatives
+ case AlternativesTreeMaker(_, altss, _) :: GuardAndBodyTreeMakers(guard, body) if alternativesSupported =>
+ val switchableAlts = altss map {
+ case SwitchableTreeMaker(pattern) :: Nil =>
+ Some(pattern)
+ case _ =>
+ None
+ }
- // succeed if they were all switchable
- sequence(switchableAlts) map { switchableAlts =>
- CaseDef(Alternative(switchableAlts), guard, body)
- }
- case _ =>
- // patmatDebug("can't emit switch for "+ makers)
- None //failure (can't translate pattern to a switch)
+ // succeed if they were all switchable
+ sequence(switchableAlts) map { switchableAlts =>
+ CaseDef(Alternative(switchableAlts), guard, body)
+ }
+ case _ =>
+ // patmatDebug("can't emit switch for "+ makers)
+ None //failure (can't translate pattern to a switch)
+ }
}
- }
- (for(
- caseDefsWithGuards <- sequence(caseDefs);
- collapsed = collapseGuardedCases(caseDefsWithGuards);
- caseDefs <- checkNoGuards(collapsed)) yield {
- if (!unchecked)
- unreachableCase(caseDefs) foreach (cd => reportUnreachable(cd.body.pos))
-
- // if we rewrote, we may need the default label to jump there (but then again, we may not)
- // TODO: make more precise; we don't need the default label if
- // - all collapsed cases included an un-guarded case (some of the guards of each case will always be true)
- // - or: there was no default case (if all the guards of a case fail, it's a matcherror for sure)
- val needDefaultLabel = (collapsed != caseDefsWithGuards)
-
- def wrapInDefaultLabelDef(cd: CaseDef): CaseDef =
- if (needDefaultLabel && canJump) deriveCaseDef(cd){ b =>
- // TODO: can b.tpe ever be null? can't really use pt, see e.g. pos/t2683 or cps/match1.scala
- defaultLabel setInfo MethodType(Nil, if (b.tpe != null) b.tpe else pt)
- LabelDef(defaultLabel, Nil, b)
- } else cd
-
- (caseDefs partition isDefault) match {
- case (Nil, caseDefs) => caseDefs :+ wrapInDefaultLabelDef(defaultCase())
- case (default :: Nil, caseDefs) if canJump || !needDefaultLabel =>
- // we either didn't collapse (and thus definitely didn't have to emit a jump),
- // or we canJump (and then the potential jumps in collapsed are ok)
- caseDefs :+ wrapInDefaultLabelDef(default)
- case _ => Nil
- // TODO: if (canJump) error message (but multiple defaults should be caught by unreachability)
- // if (!canJump) we got ourselves in the situation where we might need to emit a jump when we can't (in exception handler)
- // --> TODO: refine the condition to detect whether we actually really needed to jump, but this seems relatively rare
+ val caseDefsWithGuards = sequence(caseDefs) match {
+ case None => return Nil
+ case Some(cds) => cds
+ }
+
+ val allReachable =
+ if (unchecked) true
+ else {
+ val unreachables = unreachableCase(caseDefsWithGuards)
+ unreachables foreach {cd => reportUnreachable(cd.body.pos)}
+ // a switch with duplicate cases yields a verify error,
+ // and a switch with duplicate cases and guards cannot soundly be rewritten to an unguarded switch
+ // (even though the verify error would disappear, the behaviour would change)
+ unreachables.isEmpty
+ }
+
+ if (!allReachable) Nil
+ else if (noGuards(caseDefsWithGuards)) {
+ if (isDefault(caseDefsWithGuards.last)) caseDefsWithGuards
+ else caseDefsWithGuards :+ defaultCase()
+ } else {
+ // collapse identical cases with different guards, push guards into body for all guarded cases
+ // this translation is only sound if there are no unreachable (duplicate) cases
+ // it should only be run if there are guarded cases, and on failure it returns Nil
+ val (collapsed, needDefaultLabel) = collapseGuardedCases(caseDefsWithGuards)
+
+ if (collapsed.isEmpty || (needDefaultLabel && !canJump)) Nil
+ else {
+ def wrapInDefaultLabelDef(cd: CaseDef): CaseDef =
+ if (needDefaultLabel) deriveCaseDef(cd){ b =>
+ // TODO: can b.tpe ever be null? can't really use pt, see e.g. pos/t2683 or cps/match1.scala
+ defaultLabel setInfo MethodType(Nil, if (b.tpe != null) b.tpe else pt)
+ LabelDef(defaultLabel, Nil, b)
+ } else cd
+
+ val last = collapsed.last
+ if (isDefault(last)) {
+ if (!needDefaultLabel) collapsed
+ else collapsed.init :+ wrapInDefaultLabelDef(last)
+ } else collapsed :+ wrapInDefaultLabelDef(defaultCase())
}
}
- ) getOrElse Nil
- }
+ }
}
class RegularSwitchMaker(scrutSym: Symbol, matchFailGenOverride: Option[Tree => Tree], val unchecked: Boolean) extends SwitchMaker {
diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala
index 745065b024..7ba749ed2c 100644
--- a/src/reflect/scala/reflect/internal/TreeInfo.scala
+++ b/src/reflect/scala/reflect/internal/TreeInfo.scala
@@ -442,6 +442,9 @@ abstract class TreeInfo {
}
*/
+ /** Is this case guarded? */
+ def isGuardedCase(cdef: CaseDef) = cdef.guard != EmptyTree
+
/** Is this pattern node a sequence-valued pattern? */
def isSequenceValued(tree: Tree): Boolean = unbind(tree) match {
case Alternative(ts) => ts exists isSequenceValued