summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@epfl.ch>2012-07-02 10:55:40 +0200
committerAdriaan Moors <adriaan.moors@epfl.ch>2012-07-03 15:43:00 +0200
commit64acb46ab796a01bb5186385b7f8568748a857be (patch)
tree085582b323bceb452731ad84f23a2e3d7c228ea9
parentbc2e6fe8bf20b2aa985ea78a2c610ac33497da38 (diff)
downloadscala-64acb46ab796a01bb5186385b7f8568748a857be.tar.gz
scala-64acb46ab796a01bb5186385b7f8568748a857be.tar.bz2
scala-64acb46ab796a01bb5186385b7f8568748a857be.zip
SI-5830 switches: support guards, unreachability
turn switches with guards into guard-free switches by collecting all cases that are (possibly) guarded by different guards but that switch on the same constant, and pushing the implied if-then-else into the collapsed case body ``` case C if G1 => B1 case C if Gi => Bi case C if GN => BN ``` becomes ``` case C => if (G1) B1 else if (Gi) Bi else if (GN) BN else default() // not necessary if GN == EmptyTree ``` default() is a jump to the default case; to enable this, we wrap a default() { } labeldef around the last case's body (the user-defined default or the synthetic case that throws the matcherror) so we can jump to the default case after the last guard is checked (assuming unreachability is checked, once we ended up in a non-default case, one of the guards either matches or we go to the default case) the unreachability analysis is minimal -- we simply check (after rewriting to guard-free form) that: - there are no duplicate cases - the default case comes last misc notes: - can't jump in exception handlers (TODO: a more fine-grained analysis on when we need to jump) - work around SI-6015 (test file run/t5830.scala crashed the inliner) - propagate type of case body to label def of default case (needed for existentials, see e.g., t2683) - the default(){} LabelDef breaks SelectiveANFTransform -- workaround: don't write guarded switches in CPS code (do the above transformation manually)
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala250
-rw-r--r--test/files/neg/switch.check5
-rw-r--r--test/files/neg/switch.scala4
-rw-r--r--test/files/neg/t5830.check4
-rw-r--r--test/files/neg/t5830.flags1
-rw-r--r--test/files/neg/t5830.scala9
-rw-r--r--test/files/run/inline-ex-handlers.check58
-rw-r--r--test/files/run/t5830.check6
-rw-r--r--test/files/run/t5830.flags1
-rw-r--r--test/files/run/t5830.scala56
10 files changed, 308 insertions, 86 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala
index c466206192..70c2ddfb2d 100644
--- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala
@@ -143,6 +143,15 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
val typer: Typer
val matchOwner = typer.context.owner
+ def reportUnreachable(pos: Position) = typer.context.unit.warning(pos, "unreachable code")
+ def reportMissingCases(pos: Position, counterExamples: List[String]) = {
+ val ceString =
+ if (counterExamples.tail.isEmpty) "input: " + counterExamples.head
+ else "inputs: " + counterExamples.mkString(", ")
+
+ typer.context.unit.warning(pos, "match may not be exhaustive.\nIt would fail on the following "+ ceString)
+ }
+
def inMatchMonad(tp: Type): Type
def pureType(tp: Type): Type
final def matchMonadResult(tp: Type): Type =
@@ -792,7 +801,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type, unchecked: Boolean): (List[List[TreeMaker]], List[Tree]) =
(cases, Nil)
- def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Tree => Tree]): Option[Tree] =
+ def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Tree => Tree], unchecked: Boolean): Option[Tree] =
None
// for catch (no need to customize match failure)
@@ -1103,9 +1112,9 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
combineCasesNoSubstOnly(scrut, scrutSym, casesNoSubstOnly, pt, owner, matchFailGenOverride)
}
+ // pt is the fully defined type of the cases (either pt or the lub of the types of the cases)
def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Tree => Tree]): Tree =
fixerUpper(owner, scrut.pos){
- val ptDefined = if (isFullyDefined(pt)) pt else NoType
def matchFailGen = (matchFailGenOverride orElse Some(CODE.MATCHERROR(_: Tree)))
// patmatDebug("combining cases: "+ (casesNoSubstOnly.map(_.mkString(" >> ")).mkString("{", "\n", "}")))
@@ -1120,7 +1129,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
(false, false)
}
- emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt, matchFailGenOverride).getOrElse{
+ emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt, matchFailGenOverride, unchecked).getOrElse{
if (requireSwitch) typer.context.unit.warning(scrut.pos, "could not emit switch for @switch annotated match")
if (casesNoSubstOnly nonEmpty) {
@@ -1208,6 +1217,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
// local / context-free
def _asInstanceOf(b: Symbol, tp: Type): Tree
+ def _asInstanceOf(t: Tree, tp: Type, force: Boolean = false): Tree
def _equals(checker: Tree, binder: Symbol): Tree
def _isInstanceOf(b: Symbol, tp: Type): Tree
def and(a: Tree, b: Tree): Tree
@@ -2918,36 +2928,150 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
def alternativesSupported: Boolean
+ // when collapsing guarded switch cases we may sometimes need to jump to the default case
+ // however, that's not supported in exception handlers, so when we can't jump when we need it, don't emit a switch
+ // TODO: make more fine-grained, as we don't always need to jump
+ def canJump: Boolean
+
+ def unchecked: Boolean
+
+
def isDefault(x: CaseDef): Boolean
def defaultSym: Symbol
def defaultBody: Tree
- def defaultCase(scrutSym: Symbol = defaultSym, body: Tree = defaultBody): CaseDef
+ def defaultCase(scrutSym: Symbol = defaultSym, guard: Tree = EmptyTree, body: Tree = defaultBody): CaseDef
private def sequence[T](xs: List[Option[T]]): Option[List[T]] =
if (xs exists (_.isEmpty)) None else Some(xs.flatten)
+ object GuardAndBodyTreeMakers {
+ def unapply(tms: List[TreeMaker]): Option[(Tree, Tree)] = {
+ tms match {
+ case (btm@BodyTreeMaker(body, _)) :: Nil => Some((EmptyTree, btm.substitution(body)))
+ case (gtm@GuardTreeMaker(guard)) :: (btm@BodyTreeMaker(body, _)) :: Nil => Some((gtm.substitution(guard), btm.substitution(body)))
+ case _ => None
+ }
+ }
+ }
+
+ private val defaultLabel: Symbol = NoSymbol.newLabel(freshName("default"), NoPosition) setFlag SYNTH_CASE
+
+ /** 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}*)
+ *
+ */
+ 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
+ }
+
+ // 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)
+ }
+
+ List(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))
+ }
+
+ // common case: no rewrite needed when there are no guards
+ if (cases.forall(c => c.guard == EmptyTree)) cases
+ else partitionAndCollapse(cases)
+ }
+
+ private def caseChecksSameConst(x: CaseDef)(y: CaseDef) = (x, y) match {
+ // regular switch
+ case (CaseDef(Literal(Constant(cx)), _, _), CaseDef(Literal(Constant(cy)), _, _)) => cx == cy
+ case (CaseDef(Ident(nme.WILDCARD), _, _), CaseDef(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 _ => false
+ }
+
+ private def checkNoGuards(cs: List[CaseDef]) =
+ if (cs.exists{case CaseDef(_, g, _) => g != EmptyTree case _ => false}) None
+ else Some(cs)
+
+ // requires(cs.forall(_.guard == EmptyTree))
+ 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))
+
+ cases = cases.tail
+ }
+
+ unreachable
+ }
+
// 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 (btm@BodyTreeMaker(body, _)) :: Nil =>
- Some(defaultCase(scrutSym, btm.substitution(body)))
+ case GuardAndBodyTreeMakers(guard, body) =>
+ Some(defaultCase(scrutSym, guard, body))
// constant (or typetest for typeSwitch)
- case SwitchableTreeMaker(pattern) :: (btm@BodyTreeMaker(body, _)) :: Nil =>
- Some(CaseDef(pattern, EmptyTree, btm.substitution(body)))
+ case SwitchableTreeMaker(pattern) :: GuardAndBodyTreeMakers(guard, body) =>
+ Some(CaseDef(pattern, guard, body))
// alternatives
- case AlternativesTreeMaker(_, altss, _) :: (btm@BodyTreeMaker(body, _)) :: Nil if alternativesSupported =>
- val casePatterns = altss map {
+ case AlternativesTreeMaker(_, altss, _) :: GuardAndBodyTreeMakers(guard, body) if alternativesSupported =>
+ val switchableAlts = altss map {
case SwitchableTreeMaker(pattern) :: Nil =>
Some(pattern)
case _ =>
None
}
- sequence(casePatterns) map { patterns =>
- val substedBody = btm.substitution(body)
- CaseDef(Alternative(patterns), EmptyTree, substedBody)
+ // 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)
@@ -2955,18 +3079,45 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
}
(for(
- caseDefs <- sequence(caseDefs)) yield
- if (caseDefs exists isDefault) caseDefs
- else {
- caseDefs :+ defaultCase()
+ 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
}
+ }
) getOrElse Nil
}
}
- class RegularSwitchMaker(scrutSym: Symbol, matchFailGenOverride: Option[Tree => Tree]) extends SwitchMaker {
+ class RegularSwitchMaker(scrutSym: Symbol, matchFailGenOverride: Option[Tree => Tree], val unchecked: Boolean) extends SwitchMaker {
val switchableTpe = Set(ByteClass.tpe, ShortClass.tpe, IntClass.tpe, CharClass.tpe)
val alternativesSupported = true
+ val canJump = true
object SwitchablePattern { def unapply(pat: Tree): Option[Tree] = pat match {
case Literal(const@Constant((_: Byte ) | (_: Short) | (_: Int ) | (_: Char ))) =>
@@ -2988,13 +3139,13 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
def defaultSym: Symbol = scrutSym
def defaultBody: Tree = { import CODE._; matchFailGenOverride map (gen => gen(REF(scrutSym))) getOrElse MATCHERROR(REF(scrutSym)) }
- def defaultCase(scrutSym: Symbol = defaultSym, body: Tree = defaultBody): CaseDef = { import CODE._; atPos(body.pos) {
- DEFAULT ==> body
+ def defaultCase(scrutSym: Symbol = defaultSym, guard: Tree = EmptyTree, body: Tree = defaultBody): CaseDef = { import CODE._; atPos(body.pos) {
+ (DEFAULT IF guard) ==> body
}}
}
- override def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Tree => Tree]): Option[Tree] = { import CODE._
- val regularSwitchMaker = new RegularSwitchMaker(scrutSym, matchFailGenOverride)
+ override def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Tree => Tree], unchecked: Boolean): Option[Tree] = { import CODE._
+ val regularSwitchMaker = new RegularSwitchMaker(scrutSym, matchFailGenOverride, unchecked)
// TODO: if patterns allow switch but the type of the scrutinee doesn't, cast (type-test) the scrutinee to the corresponding switchable type and switch on the result
if (regularSwitchMaker.switchableTpe(scrutSym.tpe)) {
val caseDefsWithDefault = regularSwitchMaker(cases map {c => (scrutSym, c)}, pt)
@@ -3014,8 +3165,10 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
// for the catch-cases in a try/catch
private object typeSwitchMaker extends SwitchMaker {
+ val unchecked = false
def switchableTpe(tp: Type) = true
val alternativesSupported = false // TODO: needs either back-end support of flattening of alternatives during typers
+ val canJump = false
// TODO: there are more treemaker-sequences that can be handled by type tests
// analyze the result of approximateTreeMaker rather than the TreeMaker itself
@@ -3037,8 +3190,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
lazy val defaultSym: Symbol = freshSym(NoPosition, ThrowableClass.tpe)
def defaultBody: Tree = Throw(CODE.REF(defaultSym))
- def defaultCase(scrutSym: Symbol = defaultSym, body: Tree = defaultBody): CaseDef = { import CODE._; atPos(body.pos) {
- CASE (Bind(scrutSym, Typed(Ident(nme.WILDCARD), TypeTree(ThrowableClass.tpe)))) ==> body
+ def defaultCase(scrutSym: Symbol = defaultSym, guard: Tree = EmptyTree, body: Tree = defaultBody): CaseDef = { import CODE._; atPos(body.pos) {
+ (CASE (Bind(scrutSym, Typed(Ident(nme.WILDCARD), TypeTree(ThrowableClass.tpe)))) IF guard) ==> body
}}
}
@@ -3082,34 +3235,34 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
matchEnd setInfo MethodType(List(matchRes), restpe)
def newCaseSym = NoSymbol.newLabel(freshName("case"), NoPosition) setInfo MethodType(Nil, restpe) setFlag SYNTH_CASE
- var nextCase = newCaseSym
- def caseDef(mkCase: Casegen => Tree): Tree = {
- val currCase = nextCase
- nextCase = newCaseSym
- val casegen = new OptimizedCasegen(matchEnd, nextCase, restpe)
- LabelDef(currCase, Nil, mkCase(casegen))
+ var _currCase = newCaseSym
+
+ val caseDefs = cases map { (mkCase: Casegen => Tree) =>
+ val currCase = _currCase
+ val nextCase = newCaseSym
+ _currCase = nextCase
+
+ LabelDef(currCase, Nil, mkCase(new OptimizedCasegen(matchEnd, nextCase, restpe)))
}
- def catchAll = matchFailGen map { matchFailGen =>
- val scrutRef = if(scrutSym ne NoSymbol) REF(scrutSym) else EmptyTree // for alternatives
- // must jump to matchEnd, use result generated by matchFailGen (could be `FALSE` for isDefinedAt)
- LabelDef(nextCase, Nil, matchEnd APPLY (matchFailGen(scrutRef)))
- // don't cast the arg to matchEnd when using PartialFun synth in uncurry, since it won't detect the throw (see gen.withDefaultCase)
- // the cast is necessary when using typedMatchAnonFun-style PartialFun synth:
- // (_asInstanceOf(matchFailGen(scrutRef), restpe))
- } toList
+ // must compute catchAll after caseLabels (side-effects nextCase)
// catchAll.isEmpty iff no synthetic default case needed (the (last) user-defined case is a default)
// if the last user-defined case is a default, it will never jump to the next case; it will go immediately to matchEnd
+ val catchAllDef = matchFailGen map { matchFailGen =>
+ val scrutRef = if(scrutSym ne NoSymbol) REF(scrutSym) else EmptyTree // for alternatives
+
+ LabelDef(_currCase, Nil, matchEnd APPLY (matchFailGen(scrutRef)))
+ } toList // at most 1 element
+
+ // scrutSym == NoSymbol when generating an alternatives matcher
+ val scrutDef = if(scrutSym ne NoSymbol) List(VAL(scrutSym) === scrut) else Nil // for alternatives
// the generated block is taken apart in TailCalls under the following assumptions
// the assumption is once we encounter a case, the remainder of the block will consist of cases
// the prologue may be empty, usually it is the valdef that stores the scrut
// val (prologue, cases) = stats span (s => !s.isInstanceOf[LabelDef])
-
- // scrutSym == NoSymbol when generating an alternatives matcher
- val scrutDef = if(scrutSym ne NoSymbol) List(VAL(scrutSym) === scrut) else Nil // for alternatives
Block(
- scrutDef ++ (cases map caseDef) ++ catchAll,
+ scrutDef ++ caseDefs ++ catchAllDef,
LabelDef(matchEnd, List(matchRes), REF(matchRes))
)
}
@@ -3179,16 +3332,11 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL
override def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type, unchecked: Boolean): (List[List[TreeMaker]], List[Tree]) = {
if (!unchecked) {
unreachableCase(prevBinder, cases, pt) foreach { caseIndex =>
- typer.context.unit.warning(cases(caseIndex).last.pos, "unreachable code")
+ reportUnreachable(cases(caseIndex).last.pos)
}
- }
- val counterExamples = if (unchecked) Nil else exhaustive(prevBinder, cases, pt)
- if (counterExamples.nonEmpty) {
- val ceString =
- if (counterExamples.tail.isEmpty) "input: " + counterExamples.head
- else "inputs: " + counterExamples.mkString(", ")
-
- typer.context.unit.warning(prevBinder.pos, "match may not be exhaustive.\nIt would fail on the following "+ ceString)
+ val counterExamples = exhaustive(prevBinder, cases, pt)
+ if (counterExamples.nonEmpty)
+ reportMissingCases(prevBinder.pos, counterExamples)
}
val optCases = doCSE(prevBinder, doDCE(prevBinder, cases, pt), pt)
diff --git a/test/files/neg/switch.check b/test/files/neg/switch.check
index 8955c94b32..e4730b6459 100644
--- a/test/files/neg/switch.check
+++ b/test/files/neg/switch.check
@@ -1,10 +1,7 @@
-switch.scala:28: error: could not emit switch for @switch annotated match
- def fail1(c: Char) = (c: @switch) match {
- ^
switch.scala:38: error: could not emit switch for @switch annotated match
def fail2(c: Char) = (c: @switch @unchecked) match {
^
switch.scala:45: error: could not emit switch for @switch annotated match
def fail3(c: Char) = (c: @unchecked @switch) match {
^
-three errors found
+two errors found
diff --git a/test/files/neg/switch.scala b/test/files/neg/switch.scala
index a3dfd869d6..198583fe41 100644
--- a/test/files/neg/switch.scala
+++ b/test/files/neg/switch.scala
@@ -24,8 +24,8 @@ object Main {
case _ => false
}
- // has a guard
- def fail1(c: Char) = (c: @switch) match {
+ // has a guard, but since SI-5830 that's ok
+ def succ_guard(c: Char) = (c: @switch) match {
case 'A' | 'B' | 'C' => true
case x if x == 'A' => true
case _ => false
diff --git a/test/files/neg/t5830.check b/test/files/neg/t5830.check
new file mode 100644
index 0000000000..85cb84378f
--- /dev/null
+++ b/test/files/neg/t5830.check
@@ -0,0 +1,4 @@
+t5830.scala:6: error: unreachable code
+ case 'a' => println("b") // unreachable
+ ^
+one error found
diff --git a/test/files/neg/t5830.flags b/test/files/neg/t5830.flags
new file mode 100644
index 0000000000..e8fb65d50c
--- /dev/null
+++ b/test/files/neg/t5830.flags
@@ -0,0 +1 @@
+-Xfatal-warnings \ No newline at end of file
diff --git a/test/files/neg/t5830.scala b/test/files/neg/t5830.scala
new file mode 100644
index 0000000000..c2df3dec8b
--- /dev/null
+++ b/test/files/neg/t5830.scala
@@ -0,0 +1,9 @@
+import scala.annotation.switch
+
+class Test {
+ def unreachable(ch: Char) = (ch: @switch) match {
+ case 'a' => println("b") // ok
+ case 'a' => println("b") // unreachable
+ case 'c' =>
+ }
+} \ No newline at end of file
diff --git a/test/files/run/inline-ex-handlers.check b/test/files/run/inline-ex-handlers.check
index 708fcc6985..7d96c447b0 100644
--- a/test/files/run/inline-ex-handlers.check
+++ b/test/files/run/inline-ex-handlers.check
@@ -20,14 +20,14 @@
409c408,417
< 103 THROW(MyException)
---
-> ? STORE_LOCAL(value ex5)
+> ? STORE_LOCAL(value ex6)
> ? JUMP 17
>
> 17:
-> 101 LOAD_LOCAL(value ex5)
-> 101 STORE_LOCAL(value x3)
-> 101 SCOPE_ENTER value x3
-> 106 LOAD_LOCAL(value x3)
+> 101 LOAD_LOCAL(value ex6)
+> 101 STORE_LOCAL(value x4)
+> 101 SCOPE_ENTER value x4
+> 106 LOAD_LOCAL(value x4)
> 106 IS_INSTANCE REF(class MyException)
> 106 CZJUMP (BOOL)NE ? 5 : 11
422,424d429
@@ -125,29 +125,29 @@
737c786,793
< 172 THROW(MyException)
---
-> ? STORE_LOCAL(value ex5)
+> ? STORE_LOCAL(value ex6)
> ? JUMP 32
>
> 32:
-> 170 LOAD_LOCAL(value ex5)
-> 170 STORE_LOCAL(value x3)
-> 170 SCOPE_ENTER value x3
+> 170 LOAD_LOCAL(value ex6)
+> 170 STORE_LOCAL(value x4)
+> 170 SCOPE_ENTER value x4
> 170 JUMP 18
793c849,850
< 177 THROW(MyException)
---
-> ? STORE_LOCAL(value ex5)
+> ? STORE_LOCAL(value ex6)
> ? JUMP 33
797c854,861
< 170 THROW(Throwable)
---
-> ? STORE_LOCAL(value ex5)
+> ? STORE_LOCAL(value ex6)
> ? JUMP 33
>
> 33:
-> 169 LOAD_LOCAL(value ex5)
-> 169 STORE_LOCAL(value x3)
-> 169 SCOPE_ENTER value x3
+> 169 LOAD_LOCAL(value ex6)
+> 169 STORE_LOCAL(value x4)
+> 169 SCOPE_ENTER value x4
> 169 JUMP 5
830c894,895
< 182 THROW(MyException)
@@ -188,13 +188,13 @@
909c988,995
< 124 THROW(MyException)
---
-> ? STORE_LOCAL(value ex5)
+> ? STORE_LOCAL(value ex6)
> ? JUMP 20
>
> 20:
-> 122 LOAD_LOCAL(value ex5)
-> 122 STORE_LOCAL(value x3)
-> 122 SCOPE_ENTER value x3
+> 122 LOAD_LOCAL(value ex6)
+> 122 STORE_LOCAL(value x4)
+> 122 SCOPE_ENTER value x4
> 122 JUMP 7
969c1055
< catch (IllegalArgumentException) in ArrayBuffer(6, 7, 8, 11, 14, 16, 17, 19) starting at: 3
@@ -207,14 +207,14 @@
1019c1105,1114
< 148 THROW(MyException)
---
-> ? STORE_LOCAL(value ex5)
+> ? STORE_LOCAL(value ex6)
> ? JUMP 20
>
> 20:
-> 145 LOAD_LOCAL(value ex5)
-> 145 STORE_LOCAL(value x3)
-> 145 SCOPE_ENTER value x3
-> 154 LOAD_LOCAL(value x3)
+> 145 LOAD_LOCAL(value ex6)
+> 145 STORE_LOCAL(value x4)
+> 145 SCOPE_ENTER value x4
+> 154 LOAD_LOCAL(value x4)
> 154 IS_INSTANCE REF(class MyException)
> 154 CZJUMP (BOOL)NE ? 5 : 11
1040,1042d1134
@@ -243,19 +243,19 @@
1372c1471,1472
< 203 THROW(MyException)
---
-> ? STORE_LOCAL(value ex5)
+> ? STORE_LOCAL(value ex6)
> ? JUMP 20
1392c1492,1501
< 209 THROW(MyException)
---
-> ? STORE_LOCAL(value ex5)
+> ? STORE_LOCAL(value ex6)
> ? JUMP 20
>
> 20:
-> 200 LOAD_LOCAL(value ex5)
-> 200 STORE_LOCAL(value x3)
-> 200 SCOPE_ENTER value x3
-> 212 LOAD_LOCAL(value x3)
+> 200 LOAD_LOCAL(value ex6)
+> 200 STORE_LOCAL(value x4)
+> 200 SCOPE_ENTER value x4
+> 212 LOAD_LOCAL(value x4)
> 212 IS_INSTANCE REF(class MyException)
> 212 CZJUMP (BOOL)NE ? 5 : 11
1405,1407d1513
diff --git a/test/files/run/t5830.check b/test/files/run/t5830.check
new file mode 100644
index 0000000000..675387eb8e
--- /dev/null
+++ b/test/files/run/t5830.check
@@ -0,0 +1,6 @@
+a with oef
+a with oef
+a
+def with oef
+def
+default
diff --git a/test/files/run/t5830.flags b/test/files/run/t5830.flags
new file mode 100644
index 0000000000..e8fb65d50c
--- /dev/null
+++ b/test/files/run/t5830.flags
@@ -0,0 +1 @@
+-Xfatal-warnings \ No newline at end of file
diff --git a/test/files/run/t5830.scala b/test/files/run/t5830.scala
new file mode 100644
index 0000000000..5d808bfa28
--- /dev/null
+++ b/test/files/run/t5830.scala
@@ -0,0 +1,56 @@
+import scala.annotation.switch
+
+object Test extends App {
+ // TODO: should not emit a switch
+ // def noSwitch(ch: Char, eof: Boolean) = (ch: @switch) match {
+ // case 'a' if eof => println("a with oef") // then branch
+ // }
+
+ def onlyThen(ch: Char, eof: Boolean) = (ch: @switch) match {
+ case 'a' if eof => println("a with oef") // then branch
+ case 'c' =>
+ }
+
+ def ifThenElse(ch: Char, eof: Boolean) = (ch: @switch) match {
+ case 'a' if eof => println("a with oef") // then branch
+ case 'a' if eof => println("a with oef2") // unreachable, but the analysis is not that sophisticated
+ case 'a' => println("a") // else-branch
+ case 'c' =>
+ }
+
+ def defaultUnguarded(ch: Char, eof: Boolean) = (ch: @switch) match {
+ case ' ' if eof => println("spacey oef")
+ case _ => println("default")
+ }
+
+ def defaults(ch: Char, eof: Boolean) = (ch: @switch) match {
+ case _ if eof => println("def with oef") // then branch
+ case _ if eof => println("def with oef2") // unreachable, but the analysis is not that sophisticated
+ case _ => println("def") // else-branch
+ }
+
+ // test binders in collapsed cases (no need to run, it's "enough" to know it doesn't crash the compiler)
+ def guard(x: Any): Boolean = true
+ def testBinders =
+ try { println("") } // work around SI-6015
+ catch {
+ case _ if guard(null) =>
+ case x if guard(x) => throw x
+ }
+
+ // def unreachable(ch: Char) = (ch: @switch) match {
+ // case 'a' => println("b") // ok
+ // case 'a' => println("b") // unreachable
+ // case 'c' =>
+ // }
+
+ // noSwitch('a', true)
+ onlyThen('a', true) // 'a with oef'
+ ifThenElse('a', true) // 'a with oef'
+ ifThenElse('a', false) // 'a'
+ defaults('a', true) // 'def with oef'
+ defaults('a', false) // 'def'
+
+ // test that it jumps to default case, no match error
+ defaultUnguarded(' ', false) // default
+} \ No newline at end of file