From 64acb46ab796a01bb5186385b7f8568748a857be Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 2 Jul 2012 10:55:40 +0200 Subject: 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) --- .../tools/nsc/typechecker/PatternMatching.scala | 250 ++++++++++++++++----- test/files/neg/switch.check | 5 +- test/files/neg/switch.scala | 4 +- test/files/neg/t5830.check | 4 + test/files/neg/t5830.flags | 1 + test/files/neg/t5830.scala | 9 + test/files/run/inline-ex-handlers.check | 58 ++--- test/files/run/t5830.check | 6 + test/files/run/t5830.flags | 1 + test/files/run/t5830.scala | 56 +++++ 10 files changed, 308 insertions(+), 86 deletions(-) create mode 100644 test/files/neg/t5830.check create mode 100644 test/files/neg/t5830.flags create mode 100644 test/files/neg/t5830.scala create mode 100644 test/files/run/t5830.check create mode 100644 test/files/run/t5830.flags create mode 100644 test/files/run/t5830.scala 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 -- cgit v1.2.3