/* NSC -- new Scala compiler * * Copyright 2011-2013 LAMP/EPFL * @author Adriaan Moors */ package scala.tools.nsc.transform.patmat import scala.language.postfixOps import scala.tools.nsc.symtab.Flags.MUTABLE import scala.collection.mutable import scala.reflect.internal.util.Position /** Optimize and analyze matches based on their TreeMaker-representation. * * The patmat translation doesn't rely on this, so it could be disabled in principle. * - well, not quite: the backend crashes if we emit duplicates in switches (e.g. SI-7290) */ // TODO: split out match analysis trait MatchOptimization extends MatchTreeMaking with MatchAnalysis { import global._ import global.definitions._ //// trait CommonSubconditionElimination extends OptimizedCodegen with MatchApproximator { /** a flow-sensitive, generalised, common sub-expression elimination * reuse knowledge from performed tests * the only sub-expressions we consider are the conditions and results of the three tests (type, type&equality, equality) * when a sub-expression is shared, it is stored in a mutable variable * the variable is floated up so that its scope includes all of the program that shares it * we generalize sharing to implication, where b reuses a if a => b and priors(a) => priors(b) (the priors of a sub expression form the path through the decision tree) */ def doCSE(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): List[List[TreeMaker]] = { debug.patmat("before CSE:") showTreeMakers(cases) val testss = approximateMatchConservative(prevBinder, cases) // interpret: val dependencies = new mutable.LinkedHashMap[Test, Set[Prop]] val tested = new mutable.HashSet[Prop] // TODO: use SAT solver instead of hashconsing props and approximating implication by subset/equality def storeDependencies(test: Test) = { val cond = test.prop def simplify(c: Prop): Set[Prop] = c match { case And(ops) => ops.toSet flatMap simplify case Or(ops) => Set(False) // TODO: make more precise case Not(Eq(Var(_), NullConst)) => Set(True) // not worth remembering case _ => Set(c) } val conds = simplify(cond) if (conds(False)) false // stop when we encounter a definite "no" or a "not sure" else { val nonTrivial = conds - True if (nonTrivial nonEmpty) { tested ++= nonTrivial // is there an earlier test that checks our condition and whose dependencies are implied by ours? dependencies find { case (priorTest, deps) => ((simplify(priorTest.prop) == nonTrivial) || // our conditions are implied by priorTest if it checks the same thing directly (nonTrivial subsetOf deps) // or if it depends on a superset of our conditions ) && (deps subsetOf tested) // the conditions we've tested when we are here in the match satisfy the prior test, and hence what it tested } foreach { case (priorTest, _) => // if so, note the dependency in both tests priorTest registerReuseBy test } dependencies(test) = tested.toSet // copies } true } } testss foreach { tests => tested.clear() tests dropWhile storeDependencies } debug.patmat("dependencies: "+ dependencies) // find longest prefix of tests that reuse a prior test, and whose dependent conditions monotonically increase // then, collapse these contiguous sequences of reusing tests // store the result of the final test and the intermediate results in hoisted mutable variables (TODO: optimize: don't store intermediate results that aren't used) // replace each reference to a variable originally bound by a collapsed test by a reference to the hoisted variable val reused = new mutable.HashMap[TreeMaker, ReusedCondTreeMaker] var okToCall = false val reusedOrOrig = (tm: TreeMaker) => {assert(okToCall); reused.getOrElse(tm, tm)} // maybe collapse: replace shared prefix of tree makers by a ReusingCondTreeMaker // once this has been computed, we'll know which tree makers are reused, // and we'll replace those by the ReusedCondTreeMakers we've constructed (and stored in the reused map) val collapsed = testss map { tests => // map tests to the equivalent list of treemakers, replacing shared prefixes by a reusing treemaker // if there's no sharing, simply map to the tree makers corresponding to the tests var currDeps = Set[Prop]() val (sharedPrefix, suffix) = tests span { test => (test.prop == True) || (for( reusedTest <- test.reuses; nextDeps <- dependencies.get(reusedTest); diff <- (nextDeps -- currDeps).headOption; _ <- Some(currDeps = nextDeps)) yield diff).nonEmpty } val collapsedTreeMakers = if (sharedPrefix.isEmpty) None else { // even sharing prefixes of length 1 brings some benefit (overhead-percentage for compiler: 26->24%, lib: 19->16%) for (test <- sharedPrefix; reusedTest <- test.reuses) reusedTest.treeMaker match { case reusedCTM: CondTreeMaker => reused(reusedCTM) = ReusedCondTreeMaker(reusedCTM) case _ => } debug.patmat(s"sharedPrefix: $sharedPrefix") debug.patmat(s"suffix: $suffix") // if the shared prefix contains interesting conditions (!= True) // and the last of such interesting shared conditions reuses another treemaker's test // replace the whole sharedPrefix by a ReusingCondTreeMaker for (lastShared <- sharedPrefix.reverse.dropWhile(_.prop == True).headOption; lastReused <- lastShared.reuses) yield ReusingCondTreeMaker(sharedPrefix, reusedOrOrig) :: suffix.map(_.treeMaker) } collapsedTreeMakers getOrElse tests.map(_.treeMaker) // sharedPrefix need not be empty (but it only contains True-tests, which are dropped above) } okToCall = true // TODO: remove (debugging) // replace original treemakers that are reused (as determined when computing collapsed), // by ReusedCondTreeMakers val reusedMakers = collapsed mapConserve (_ mapConserve reusedOrOrig) debug.patmat("after CSE:") showTreeMakers(reusedMakers) reusedMakers } object ReusedCondTreeMaker { def apply(orig: CondTreeMaker) = new ReusedCondTreeMaker(orig.prevBinder, orig.nextBinder, orig.cond, orig.res, orig.pos) } class ReusedCondTreeMaker(prevBinder: Symbol, val nextBinder: Symbol, cond: Tree, res: Tree, val pos: Position) extends TreeMaker { lazy val localSubstitution = Substitution(List(prevBinder), List(CODE.REF(nextBinder))) lazy val storedCond = freshSym(pos, BooleanTpe, "rc") setFlag MUTABLE lazy val treesToHoist: List[Tree] = { nextBinder setFlag MUTABLE List(storedCond, nextBinder) map (b => ValDef(b, codegen.mkZero(b.info))) } // TODO: finer-grained duplication def chainBefore(next: Tree)(casegen: Casegen): Tree = // assert(codegen eq optimizedCodegen) atPos(pos)(casegen.asInstanceOf[optimizedCodegen.OptimizedCasegen].flatMapCondStored(cond, storedCond, res, nextBinder, substitution(next).duplicate)) override def toString = "Memo"+((nextBinder.name, storedCond.name, cond, res, substitution)) } case class ReusingCondTreeMaker(sharedPrefix: List[Test], toReused: TreeMaker => TreeMaker) extends TreeMaker { import CODE._ val pos = sharedPrefix.last.treeMaker.pos lazy val localSubstitution = { // replace binder of each dropped treemaker by corresponding binder bound by the most recent reused treemaker var mostRecentReusedMaker: ReusedCondTreeMaker = null def mapToStored(droppedBinder: Symbol) = if (mostRecentReusedMaker eq null) Nil else List((droppedBinder, REF(mostRecentReusedMaker.nextBinder))) val (from, to) = sharedPrefix.flatMap { dropped => dropped.reuses.map(test => toReused(test.treeMaker)).foreach { case reusedMaker: ReusedCondTreeMaker => mostRecentReusedMaker = reusedMaker case _ => } // TODO: have super-trait for retrieving the variable that's operated on by a tree maker // and thus assumed in scope, either because it binds it or because it refers to it dropped.treeMaker match { case dropped: FunTreeMaker => mapToStored(dropped.nextBinder) case _ => Nil } }.unzip val rerouteToReusedBinders = Substitution(from, to) val collapsedDroppedSubst = sharedPrefix map (t => (toReused(t.treeMaker).substitution)) collapsedDroppedSubst.foldLeft(rerouteToReusedBinders)(_ >> _) } lazy val lastReusedTreeMaker = sharedPrefix.reverse.flatMap(tm => tm.reuses map (test => toReused(test.treeMaker))).collectFirst{case x: ReusedCondTreeMaker => x}.head def chainBefore(next: Tree)(casegen: Casegen): Tree = { // TODO: finer-grained duplication -- MUST duplicate though, or we'll get VerifyErrors since sharing trees confuses lambdalift, // and in its confusion it emits illegal casts (diagnosed by Grzegorz: checkcast T ; invokevirtual S.m, where T not a subtype of S) casegen.ifThenElseZero(REF(lastReusedTreeMaker.storedCond), substitution(next).duplicate) } override def toString = "R"+((lastReusedTreeMaker.storedCond.name, substitution)) } } //// DCE // trait DeadCodeElimination extends TreeMakers { // // TODO: non-trivial dead-code elimination // // e.g., the following match should compile to a simple instanceof: // // case class Ident(name: String) // // for (Ident(name) <- ts) println(name) // def doDCE(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): List[List[TreeMaker]] = { // // do minimal DCE // cases // } // } //// SWITCHES -- TODO: operate on Tests rather than TreeMakers trait SwitchEmission extends TreeMakers with MatchMonadInterface { import treeInfo.isGuardedCase abstract class SwitchMaker { abstract class SwitchableTreeMakerExtractor { def unapply(x: TreeMaker): Option[Tree] } val SwitchableTreeMaker: SwitchableTreeMakerExtractor 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 /** Should exhaustivity analysis be skipped? */ def unchecked: Boolean def isDefault(x: CaseDef): Boolean def defaultSym: Symbol def defaultBody: Tree def defaultCase(scrutSym: Symbol = defaultSym, guard: Tree = EmptyTree, body: Tree = defaultBody): CaseDef 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 = newSynthCaseLabel("default") /** Collapse guarded cases that switch on the same constant (the last case may be unguarded). * * 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]): (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 unguarded (we know it's the last one since fold's accum == jumpToDefault) // --> replace jumpToDefault by the unguarded 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 = 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) } atPos(commonPattern.pos)(CaseDef(pat, EmptyTree, guardedBodySubst)) } // requires cases.exists(isGuardedCase) (otherwise the rewrite is pointless) var remainingCases = cases val collapsed = scala.collection.mutable.ListBuffer.empty[CaseDef] // when some of collapsed cases (except for the default case itself) did not include an unguarded 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 } } (collapsed.toList, needDefault) } 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 (Literal(Constant(cx)), Literal(Constant(cy))) => cx == cy case (Ident(nme.WILDCARD), Ident(nme.WILDCARD)) => true // type-switch for catch case (Bind(_, Typed(Ident(nme.WILDCARD), tpX)), Bind(_, Typed(Ident(nme.WILDCARD), tpY))) => tpX.tpe =:= tpY.tpe case _ => false } // 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) // must do this before removing guards from cases and collapsing (SI-6011, SI-6048) private def unreachableCase(cases: List[CaseDef]): Option[CaseDef] = { def loop(cases: List[CaseDef]): Option[CaseDef] = cases match { case head :: next :: _ if isDefault(head) => Some(next) // subsumed by the next case, but faster case head :: rest if !isGuardedCase(head) || head.guard.tpe =:= ConstantTrue => rest find caseImplies(head) orElse loop(rest) case head :: _ if head.guard.tpe =:= ConstantFalse => Some(head) case _ :: rest => loop(rest) case _ => None } loop(cases) } // empty list ==> failure 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, pos) :: 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 => def extractConst(t: Tree) = t match { case Literal(const) => const case _ => t } // SI-7290 Discard duplicate alternatives that would crash the backend val distinctAlts = distinctBy(switchableAlts)(extractConst) if (distinctAlts.size < switchableAlts.size) { val duplicated = switchableAlts.groupBy(extractConst).flatMap(_._2.drop(1).take(1)) // report the first duplicated reporter.warning(pos, s"Pattern contains duplicate alternatives: ${duplicated.mkString(", ")}") } CaseDef(Alternative(distinctAlts), guard, body) } case _ => // debug.patmat("can't emit switch for "+ makers) None //failure (can't translate pattern to a switch) } } val caseDefsWithGuards = sequence(caseDefs) match { case None => return Nil case Some(cds) => cds } // 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) val allReachable = unreachableCase(caseDefsWithGuards) map (cd => reportUnreachable(cd.body.pos)) 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()) } } } } class RegularSwitchMaker(scrutSym: Symbol, matchFailGenOverride: Option[Tree => Tree], val unchecked: Boolean) extends SwitchMaker { val switchableTpe = Set(ByteTpe, ShortTpe, IntTpe, CharTpe) val alternativesSupported = true val canJump = true // Constant folding sets the type of a constant tree to `ConstantType(Constant(folded))` // The tree itself can be a literal, an ident, a selection, ... object SwitchablePattern { def unapply(pat: Tree): Option[Tree] = pat.tpe match { case ConstantType(const) if const.isIntRange => Some(Literal(Constant(const.intValue))) // TODO: Java 7 allows strings in switches case _ => None }} object SwitchableTreeMaker extends SwitchableTreeMakerExtractor { def unapply(x: TreeMaker): Option[Tree] = x match { case EqualityTestTreeMaker(_, SwitchablePattern(const), _) => Some(const) case _ => None } } def isDefault(x: CaseDef): Boolean = x match { case CaseDef(Ident(nme.WILDCARD), EmptyTree, _) => true case _ => false } def defaultSym: Symbol = scrutSym def defaultBody: Tree = { import CODE._; matchFailGenOverride map (gen => gen(REF(scrutSym))) getOrElse Throw(MatchErrorClass.tpe, REF(scrutSym)) } 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], 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(dealiasWiden(scrutSym.tpe))) { val caseDefsWithDefault = regularSwitchMaker(cases map {c => (scrutSym, c)}, pt) if (caseDefsWithDefault isEmpty) None // not worth emitting a switch. else { // match on scrutSym -- converted to an int if necessary -- not on scrut directly (to avoid duplicating scrut) val scrutToInt: Tree = if (scrutSym.tpe =:= IntTpe) REF(scrutSym) else (REF(scrutSym) DOT (nme.toInt)) Some(BLOCK( ValDef(scrutSym, scrut), Match(scrutToInt, caseDefsWithDefault) // a switch )) } } else None } // for the catch-cases in a try/catch private object typeSwitchMaker extends SwitchMaker { val unchecked = false 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 object SwitchableTreeMaker extends SwitchableTreeMakerExtractor { def unapply(x: TreeMaker): Option[Tree] = x match { case tm@TypeTestTreeMaker(_, _, pt, _) if tm.isPureTypeTest => // -- TODO: use this if binder does not occur in the body Some(Bind(tm.nextBinder, Typed(Ident(nme.WILDCARD), TypeTree(pt)) /* not used by back-end */)) case _ => None } } def isDefault(x: CaseDef): Boolean = x match { case CaseDef(Typed(Ident(nme.WILDCARD), tpt), EmptyTree, _) if (tpt.tpe =:= ThrowableTpe) => true case CaseDef(Bind(_, Typed(Ident(nme.WILDCARD), tpt)), EmptyTree, _) if (tpt.tpe =:= ThrowableTpe) => true case CaseDef(Ident(nme.WILDCARD), EmptyTree, _) => true case _ => false } lazy val defaultSym: Symbol = freshSym(NoPosition, ThrowableTpe) def defaultBody: Tree = Throw(CODE.REF(defaultSym)) 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(ThrowableTpe)))) IF guard) ==> body }} } // TODO: drop null checks override def emitTypeSwitch(bindersAndCases: List[(Symbol, List[TreeMaker])], pt: Type): Option[List[CaseDef]] = { val caseDefsWithDefault = typeSwitchMaker(bindersAndCases, pt) if (caseDefsWithDefault isEmpty) None else Some(caseDefsWithDefault) } } trait MatchOptimizer extends OptimizedCodegen with SwitchEmission with CommonSubconditionElimination { override def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): (List[List[TreeMaker]], List[Tree]) = { // TODO: do CSE on result of doDCE(prevBinder, cases, pt) val optCases = doCSE(prevBinder, cases, pt) val toHoist = ( for (treeMakers <- optCases) yield treeMakers.collect{case tm: ReusedCondTreeMaker => tm.treesToHoist} ).flatten.flatten.toList (optCases, toHoist) } } }