From a57ac60dbb115b90ca1ccc9944199bf3e82d517d Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 13 Feb 2012 11:54:10 +0100 Subject: [vpm] working type switch for try/catch catch handlers are now also translated by the virtualizing pattern matcher emit type-based switches when possible, fallback to catch-all with full pattern match (the logic used to emit constant-based switches was generalized to type-based switches, abstracting out how to refer to the scrutinee and the shape of a default/catch-all case) this was the last missing piece of essential functionality now the virtualizing pattern matcher and the old one are mutually exclusive: -Yvirtpatmat disables the old matcher logic completely --- .../scala/tools/nsc/backend/icode/GenICode.scala | 14 +- .../scala/tools/nsc/transform/ExplicitOuter.scala | 2 +- .../scala/tools/nsc/transform/UnCurry.scala | 3 +- .../tools/nsc/typechecker/PatMatVirtualiser.scala | 320 ++++++++++++++------- .../scala/tools/nsc/typechecker/Typers.scala | 5 + test/files/run/virtpatmat_switch.scala | 8 +- test/files/run/virtpatmat_try.check | 2 + test/files/run/virtpatmat_try.flags | 1 + test/files/run/virtpatmat_try.scala | 47 +++ 9 files changed, 295 insertions(+), 107 deletions(-) create mode 100644 test/files/run/virtpatmat_try.check create mode 100644 test/files/run/virtpatmat_try.flags create mode 100644 test/files/run/virtpatmat_try.scala diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 6aee52a354..f2c725a5c2 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -393,15 +393,15 @@ abstract class GenICode extends SubComponent { for (CaseDef(pat, _, body) <- catches.reverse) yield { def genWildcardHandler(sym: Symbol): (Symbol, TypeKind, Context => Context) = (sym, kind, ctx => { - ctx.bb.emit(DROP(REFERENCE(sym))) + ctx.bb.emit(DROP(REFERENCE(sym))) // drop the loaded exception genLoad(body, ctx, kind) }) pat match { case Typed(Ident(nme.WILDCARD), tpt) => genWildcardHandler(tpt.tpe.typeSymbol) case Ident(nme.WILDCARD) => genWildcardHandler(ThrowableClass) - case Bind(name, _) => - val exception = ctx.method addLocal new Local(pat.symbol, toTypeKind(pat.symbol.tpe), false) + case Bind(_, _) => + val exception = ctx.method addLocal new Local(pat.symbol, toTypeKind(pat.symbol.tpe), false) // the exception will be loaded and stored into this local (pat.symbol.tpe.typeSymbol, kind, { ctx: Context => @@ -1054,7 +1054,7 @@ abstract class GenICode extends SubComponent { case Match(selector, cases) => debuglog("Generating SWITCH statement."); - var ctx1 = genLoad(selector, ctx, INT) + var ctx1 = genLoad(selector, ctx, INT) // TODO: Java 7 allows strings in switches (so, don't assume INT and don't convert the literals using intValue) val afterCtx = ctx1.newBlock var caseCtx: Context = null generatedType = toTypeKind(tree.tpe) @@ -2086,12 +2086,12 @@ abstract class GenICode extends SubComponent { exh }) else None - val exhs = handlers.map { handler => - val exh = this.newExceptionHandler(handler._1, handler._2, tree.pos) + val exhs = handlers.map { case (sym, kind, handler) => // def genWildcardHandler(sym: Symbol): (Symbol, TypeKind, Context => Context) = + val exh = this.newExceptionHandler(sym, kind, tree.pos) var ctx1 = outerCtx.enterExceptionHandler(exh) ctx1.addFinalizer(finalizer, finalizerCtx) loadException(ctx1, exh, tree.pos) - ctx1 = handler._3(ctx1) + ctx1 = handler(ctx1) // emit finalizer val ctx2 = emitFinalizer(ctx1) ctx2.bb.closeWith(JUMP(afterCtx.bb)) diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 7f7f7e7b65..252b3ccffc 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -517,7 +517,7 @@ abstract class ExplicitOuter extends InfoTransform super.transform(treeCopy.Apply(tree, sel, outerVal :: args)) // entry point for pattern matcher translation - case mch: Match => + case mch: Match if (!opt.virtPatmat) => // don't use old pattern matcher as fallback when the user wants the virtualizing one matchTranslation(mch) case _ => diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 02c09a08c4..c26b19b04d 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -679,7 +679,8 @@ abstract class UnCurry extends InfoTransform if (dd.symbol hasAnnotation VarargsClass) addJavaVarargsForwarders(dd, flatdd, tree) flatdd case Try(body, catches, finalizer) => - if (catches forall treeInfo.isCatchCase) tree + if (opt.virtPatmat) { if(catches exists (cd => !treeInfo.isCatchCase(cd))) debugwarn("VPM BUG! illegal try/catch "+ catches); tree } + else if (catches forall treeInfo.isCatchCase) tree else { val exname = unit.freshTermName("ex$") val cases = diff --git a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala index 6d31243fd0..f2b9218968 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala @@ -116,6 +116,10 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => trait MatchTranslation extends MatchMonadInterface { self: TreeMakers with CodegenCore => import typer.{typed, context, silent, reallyExists} + private def repeatedToSeq(tp: Type): Type = (tp baseType RepeatedParamClass) match { + case TypeRef(_, RepeatedParamClass, args) => appliedType(SeqClass.typeConstructor, args) + case _ => tp + } /** Implement a pattern match by turning its cases (including the implicit failure case) * into the corresponding (monadic) extractors, and combining them with the `orElse` combinator. @@ -133,11 +137,6 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => // and the only place that emits Matches after typers is for exception handling anyway) assert(phase.id <= currentRun.typerPhase.id, phase) - def repeatedToSeq(tp: Type): Type = (tp baseType RepeatedParamClass) match { - case TypeRef(_, RepeatedParamClass, args) => appliedType(SeqClass.typeConstructor, args) - case _ => tp - } - val scrutType = repeatedToSeq(elimAnonymousClass(scrut.tpe.widen)) val scrutSym = freshSym(scrut.pos, pureType(scrutType)) @@ -146,6 +145,47 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => combineCases(scrut, scrutSym, cases map translateCase(scrutSym, okPt), okPt, matchOwner) } + // return list of typed CaseDefs that are supported by the backend (typed/bind/wildcard) + // we don't have a global scrutinee -- the caught exception must be bound in each of the casedefs + // there's no need to check the scrutinee for null -- "throw null" becomes "throw new NullPointerException" + // try to simplify to a type-based switch, or fall back to a catch-all case that runs a normal pattern match + // unlike translateMatch, we type our result before returning it + def translateTry(caseDefs: List[CaseDef], pt: Type, pos: Position): List[CaseDef] = + // if they're already simple enough to be handled by the back-end, we're done + if (caseDefs forall treeInfo.isCatchCase) caseDefs + else { + val okPt = repeatedToSeq(pt) + val switch = { + val bindersAndCases = caseDefs map { caseDef => + // generate a fresh symbol for each case, hoping we'll end up emitting a type-switch (we don't have a global scrut there) + // if we fail to emit a fine-grained switch, have to do translateCase again with a single scrutSym (TODO: uniformize substitution on treemakers so we can avoid this) + val caseScrutSym = freshSym(pos, pureType(ThrowableClass.tpe)) + (caseScrutSym, propagateSubstitution(translateCase(caseScrutSym, okPt)(caseDef), EmptySubstitution)) + } + + (emitTypeSwitch(bindersAndCases, pt) map (_.map(fixerUpper(matchOwner, pos).apply(_).asInstanceOf[CaseDef]))) + } + + val catches = switch getOrElse { + val scrutSym = freshSym(pos, pureType(ThrowableClass.tpe)) + val casesNoSubstOnly = caseDefs map { caseDef => (propagateSubstitution(translateCase(scrutSym, okPt)(caseDef), EmptySubstitution))} + + val exSym = freshSym(pos, pureType(ThrowableClass.tpe), "ex") + + List( + atPos(pos) { + CaseDef( + Bind(exSym, Ident(nme.WILDCARD)), // TODO: does this need fixing upping? + EmptyTree, + combineCasesNoSubstOnly(CODE.REF(exSym), scrutSym, casesNoSubstOnly, pt, matchOwner, scrut => Throw(CODE.REF(exSym))) + ) + }) + } + + typer.typedCases(catches, ThrowableClass.tpe, WildcardType) + } + + /** The translation of `pat if guard => body` has two aspects: * 1) the substitution due to the variables bound by patterns @@ -668,6 +708,10 @@ class Foo(x: Other) { x._1 } // no error in this order def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type): Option[Tree] = None + // for catch + def emitTypeSwitch(bindersAndCases: List[(Symbol, List[TreeMaker])], pt: Type): Option[List[CaseDef]] = + None + abstract class TreeMaker { /** captures the scope and the value of the bindings in patterns * important *when* the substitution happens (can't accumulate and do at once after the full matcher has been constructed) @@ -788,6 +832,7 @@ class Foo(x: Other) { x._1 } // no error in this order } // implements the run-time aspects of (ยง8.2) (typedPattern has already done the necessary type transformations) + // TODO: normalize construction, which yields a combination of a EqualityTestTreeMaker (when necessary) and a TypeTestTreeMaker case class TypeAndEqualityTestTreeMaker(prevBinder: Symbol, patBinder: Symbol, pt: Type, pos: Position) extends CondTreeMaker { val nextBinderTp = glb(List(patBinder.info.widen, pt)) @@ -843,6 +888,10 @@ class Foo(x: Other) { x._1 } // no error in this order val cond = typeAndEqualityTest(patBinder, pt) val res = codegen._asInstanceOf(patBinder, nextBinderTp) + + // TODO: remove this + def isStraightTypeTest = cond match { case TypeApply(_, _) => cond.symbol == Any_isInstanceOf case _ => false } + override def toString = "TET"+(patBinder, pt) } @@ -926,25 +975,30 @@ class Foo(x: Other) { x._1 } // no error in this order } // calls propagateSubstitution on the treemakers - def combineCases(scrut: Tree, scrutSym: Symbol, casesRaw: List[List[TreeMaker]], pt: Type, owner: Symbol): Tree = fixerUpper(owner, scrut.pos){ - val casesUnOpt = casesRaw map (propagateSubstitution(_, EmptySubstitution)) // drops SubstOnlyTreeMakers, since their effect is now contained in the TreeMakers that follow them + def combineCases(scrut: Tree, scrutSym: Symbol, casesRaw: List[List[TreeMaker]], pt: Type, owner: Symbol): Tree = { + val casesNoSubstOnly = casesRaw map (propagateSubstitution(_, EmptySubstitution)) // drops SubstOnlyTreeMakers, since their effect is now contained in the TreeMakers that follow them + combineCasesNoSubstOnly(scrut, scrutSym, casesNoSubstOnly, pt, owner, CODE.MATCHERROR(_)) + } - emitSwitch(scrut, scrutSym, casesUnOpt, pt).getOrElse{ + def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFail: Tree => Tree): Tree = fixerUpper(owner, scrut.pos){ + emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt).getOrElse{ val (matcher, hasDefault, toHoist) = - if (casesUnOpt nonEmpty) { + if (casesNoSubstOnly nonEmpty) { // when specified, need to propagate pt explicitly (type inferencer can't handle it) val optPt = if (isFullyDefined(pt)) inMatchMonad(pt) else NoType - // do this check on casesUnOpt, since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one + // do this check on casesNoSubstOnly, since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one // exhaustivity and reachability must be checked before optimization as well - val hasDefault = casesUnOpt.nonEmpty && { - val nonTrivLast = casesUnOpt.last + // TODO: improve, a trivial type test before the body still makes for a default case + // ("trivial" depends on whether we're emitting a straight match or an exception, or more generally, any supertype of scrutSym.tpe is a no-op) + val hasDefault = casesNoSubstOnly.nonEmpty && { + val nonTrivLast = casesNoSubstOnly.last nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] } - val (cases, toHoist) = optimizeCases(scrutSym, casesUnOpt, pt) + val (cases, toHoist) = optimizeCases(scrutSym, casesNoSubstOnly, pt) val combinedCases = cases.map(combineExtractors(_, pt)).reduceLeft(codegen.typedOrElse(optPt)) @@ -952,7 +1006,11 @@ class Foo(x: Other) { x._1 } // no error in this order (combinedCases, hasDefault, toHoist) } else (codegen.zero, false, Nil) - val expr = codegen.runOrElse(scrut, scrutSym, matcher, if (isFullyDefined(pt)) pt else NoType, hasDefault) + // catch-all + val catchAll = + if (hasDefault) None // no need for a catch-all when there's already a default + else Some(matchFail) + val expr = codegen.runOrElse(scrut, scrutSym, matcher, if (isFullyDefined(pt)) pt else NoType, catchAll) if (toHoist isEmpty) expr else Block(toHoist, expr) } @@ -966,7 +1024,7 @@ class Foo(x: Other) { x._1 } // no error in this order // TODO: do this during tree construction, but that will require tracking the current owner in treemakers // TODO: assign more fine-grained positions // fixes symbol nesting, assigns positions - private def fixerUpper(origOwner: Symbol, pos: Position) = new Traverser { + protected def fixerUpper(origOwner: Symbol, pos: Position) = new Traverser { currentOwner = origOwner override def traverse(t: Tree) { @@ -1019,7 +1077,7 @@ class Foo(x: Other) { x._1 } // no error in this order // codegen relevant to the structure of the translation (how extractors are combined) trait AbsCodegen { - def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, hasDefault: Boolean): Tree + def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, catchAll: Option[Tree => Tree]): Tree def one(res: Tree, bodyPt: Type, matchPt: Type): Tree def zero: Tree def flatMap(prev: Tree, b: Symbol, next: Tree): Tree @@ -1110,7 +1168,8 @@ class Foo(x: Other) { x._1 } // no error in this order object pureCodegen extends CommonCodegen { import CODE._ //// methods in MatchingStrategy (the monad companion) -- used directly in translation // __match.runOrElse(`scrut`)(`scrutSym` => `matcher`) - def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, hasDefault: Boolean): Tree + // TODO: consider catchAll, or virtualized matching will break in exception handlers + def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, catchAll: Option[Tree => Tree]): Tree = __match(vpmName.runOrElse) APPLY (scrut) APPLY (fun(scrutSym, matcher)) // __match.one(`res`) def one(res: Tree, bodyPt: Type, matchPt: Type): Tree = (__match(vpmName.one)) (res) @@ -1437,95 +1496,163 @@ class Foo(x: Other) { x._1 } // no error in this order } } - //// SWITCHES + //// SWITCHES -- TODO: operate on Tests rather than TreeMakers trait SwitchEmission extends TreeMakers with OptimizedMatchMonadInterface { self: CodegenCore => - object SwitchablePattern { def unapply(pat: Tree) = pat match { - case Literal(Constant((_: Byte ) | (_: Short) | (_: Int ) | (_: Char ))) => true // TODO: Java 7 allows strings in switches - case _ => false - }} - - // def isSwitchable(cases: List[(List[TreeMaker], Tree)]): Boolean = { - // def isSwitchableTreeMaker(tm: TreeMaker) = tm match { - // case tm@EqualityTestTreeMaker(_, SwitchablePattern(), _) => true - // case SubstOnlyTreeMaker(_) => true - // case AlternativesTreeMaker(_, altss, _) => altss forall (_.forall(isSwitchableTreeMaker)) - // case _ => false - // } - // } + abstract class SwitchMaker { + abstract class SwitchableTreeMakerExtractor { def unapply(x: TreeMaker): Option[Tree] } + val SwitchableTreeMaker: SwitchableTreeMakerExtractor + + def alternativesSupported: Boolean - private val switchableTpes = Set(ByteClass.tpe, ShortClass.tpe, IntClass.tpe, CharClass.tpe) + def isDefault(x: CaseDef): Boolean + def defaultSym(): Symbol + def defaultBody(scrutSym: Symbol): Tree + def defaultCase(scrutSym: Symbol, body: Tree): CaseDef + def switchCase(scrutSym: Symbol, pattern: Tree, body: Tree): CaseDef - override def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type): Option[Tree] = { - def sequence[T](xs: List[Option[T]]): Option[List[T]] = + private def sequence[T](xs: List[Option[T]]): Option[List[T]] = if (xs exists (_.isEmpty)) None else Some(xs.flatten) - def isSwitchableTpe(tpe: Type): Boolean = - switchableTpes contains tpe - def switchableConstToInt(x: Tree): Tree = { - val Literal(const) = x - const.tag match { - case IntTag => x - case ByteTag | ShortTag | CharTag => Literal(Constant(const.intValue)) + // 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))) + // constant (or typetest for typeSwitch) + case SwitchableTreeMaker(pattern) :: (btm@BodyTreeMaker(body, _)) :: Nil => + Some(switchCase(scrutSym, pattern, btm.substitution(body))) + // alternatives + case AlternativesTreeMaker(_, altss, _) :: (btm@BodyTreeMaker(body, _)) :: Nil if alternativesSupported => + val casePatterns = altss map { + case SwitchableTreeMaker(pattern) :: Nil => + Some(pattern) + case _ => + None + } + + sequence(casePatterns) map { patterns => + val substedBody = btm.substitution(body) + CaseDef(Alternative(patterns), EmptyTree, substedBody) + } + case _ => //println("can't emit switch for "+ makers) + None //failure (can't translate pattern to a switch) + } } - } - val caseDefs = cases map { makers => - removeSubstOnly(makers) match { - // default case (don't move this to unfold, as it may only occur on the top level, not as an alternative -- well, except in degenerate matches) - case (btm@BodyTreeMaker(body, _)) :: Nil => - Some(CaseDef(Ident(nme.WILDCARD), EmptyTree, btm.substitution(body))) - // constant - case (EqualityTestTreeMaker(_, const@SwitchablePattern(), _)) :: (btm@BodyTreeMaker(body, _)) :: Nil => - Some(CaseDef(switchableConstToInt(const), EmptyTree, btm.substitution(body))) - // alternatives - case AlternativesTreeMaker(_, altss, _) :: (btm@BodyTreeMaker(body, _)) :: Nil => // assert(currLabel.isEmpty && nextLabel.isEmpty) - val caseConstants = altss map { - case EqualityTestTreeMaker(_, const@SwitchablePattern(), _) :: Nil => - Some(switchableConstToInt(const)) - case _ => - None + (for( + caseDefs <- sequence(caseDefs)) yield + if (caseDefs exists isDefault) caseDefs + else { + val sym = defaultSym() + caseDefs :+ defaultCase(sym, defaultBody(sym)) } + ) getOrElse Nil + } + } - sequence(caseConstants) map { contants => - val substedBody = btm.substitution(body) - CaseDef(Alternative(contants), EmptyTree, substedBody) - } - case _ => - None //failure (can't translate pattern to a switch) + class RegularSwitchMaker(scrutSym: Symbol) extends SwitchMaker { + val switchableTpe = Set(ByteClass.tpe, ShortClass.tpe, IntClass.tpe, CharClass.tpe) + val alternativesSupported = true + + object SwitchablePattern { def unapply(pat: Tree): Option[Tree] = pat match { + case Literal(const@Constant((_: Byte ) | (_: Short) | (_: Int ) | (_: Char ))) => + 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 } } - if (!isSwitchableTpe(scrut.tpe)) - None // TODO: emit a cast of the scrutinee and a switch on the cast scrutinee if patterns allow switch but the type of the scrutinee doesn't - else { - sequence(caseDefs) map { caseDefs => - import CODE._ - val caseDefsWithDefault = { - def isDefault(x: CaseDef): Boolean = x match { - case CaseDef(Ident(nme.WILDCARD), EmptyTree, _) => true - case _ => false - } - val hasDefault = caseDefs exists isDefault - if (hasDefault) caseDefs else { - val default = atPos(scrut.pos) { DEFAULT ==> MATCHERROR(REF(scrutSym)) } - caseDefs :+ default - } - } - val matcher = BLOCK( - if (scrut.tpe != IntClass.tpe) { - scrutSym setInfo IntClass.tpe - VAL(scrutSym) === (scrut DOT nme.toInt) - } else { - VAL(scrutSym) === scrut - }, - Match(REF(scrutSym), caseDefsWithDefault) // match on scrutSym, not scrut to avoid duplicating scrut - ) - // matcher filter (tree => tree.tpe == null) foreach println - // treeBrowser browse matcher - matcher // set type to avoid recursion in typedMatch + def isDefault(x: CaseDef): Boolean = x match { + case CaseDef(Ident(nme.WILDCARD), EmptyTree, _) => true + case _ => false + } + + def defaultSym(): Symbol = scrutSym + def defaultBody(scrutSym: Symbol): Tree = { import CODE._ + MATCHERROR(REF(scrutSym)) + } + + def defaultCase(scrutSym: Symbol, body: Tree): CaseDef = { import CODE._ + atPos(body.pos) { DEFAULT ==> body } + } + + def switchCase(scrutSym: Symbol, pattern: Tree, body: Tree): CaseDef = + CaseDef(pattern, EmptyTree, body) + } + + override def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type): Option[Tree] = { import CODE._ + val regularSwitchMaker = new RegularSwitchMaker(scrutSym) + // 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) + if (caseDefsWithDefault isEmpty) None + else { + // match on scrutSym -- converted to an int if necessary -- not on scrut directly (to avoid duplicating scrut) + val scrutToInt: Tree = + if(scrutSym.tpe =:= IntClass.tpe) REF(scrutSym) + else (REF(scrutSym) DOT (nme.toInt)) + Some(BLOCK( + VAL(scrutSym) === scrut, + Match(scrutToInt, caseDefsWithDefault) + )) } + } else None + } + + // for the catch-cases in a try/catch + private object typeSwitchMaker extends SwitchMaker { + def switchableTpe(tp: Type) = true + val alternativesSupported = false // TODO: needs either back-end support of flattening of alternatives during typers + + // 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(_, _, _) => + Some(Bind(tm.nextBinder, Typed(Ident(nme.WILDCARD), TypeTree(tm.nextBinderTp)) /* not used by back-end */)) // -- TODO: use this if binder does not occur in the body + case tm@TypeAndEqualityTestTreeMaker(_, patBinder, pt, _) if tm.isStraightTypeTest => + Some(Bind(tm.nextBinder, Typed(Ident(nme.WILDCARD), TypeTree(tm.nextBinderTp)) /* 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 =:= ThrowableClass.tpe) => true + case CaseDef(Bind(_, Typed(Ident(nme.WILDCARD), tpt)), EmptyTree, _) if (tpt.tpe =:= ThrowableClass.tpe) => true + case CaseDef(Ident(nme.WILDCARD), EmptyTree, _) => true + case _ => false } + + def defaultSym(): Symbol = freshSym(NoPosition, ThrowableClass.tpe) + def defaultBody(scrutSym: Symbol): Tree = Throw(CODE.REF(scrutSym)) + def defaultCase(scrutSym: Symbol, body: Tree): CaseDef = { import CODE._ ; atPos(scrutSym.pos) { + CASE (Bind(scrutSym, Typed(Ident(nme.WILDCARD), TypeTree(ThrowableClass.tpe)))) ==> body + }} + + def switchCase(scrutSym: Symbol, pattern: Tree, body: Tree): CaseDef = { import CODE._ ; atPos(scrutSym.pos) { + pattern match { + case Bind(_, _) => CASE (pattern) ==> body + case _ => CASE (Bind(scrutSym, pattern)) ==> 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 OptimizedMatchMonadInterface extends MatchMonadInterface { @@ -1551,24 +1678,23 @@ class Foo(x: Other) { x._1 } // no error in this order /** Inline runOrElse and get rid of Option allocations * - * runOrElse(scrut: scrutTp)(matcher): resTp = matcher(scrut) getOrElse (throw new MatchError(x)) + * runOrElse(scrut: scrutTp)(matcher): resTp = matcher(scrut) getOrElse ${catchAll(`scrut`)} * the matcher's optional result is encoded as a flag, keepGoing, where keepGoing == true encodes result.isEmpty, * if keepGoing is false, the result Some(x) of the naive translation is encoded as matchRes == x */ @inline private def dontStore(tp: Type) = (tp.typeSymbol eq UnitClass) || (tp.typeSymbol eq NothingClass) lazy val keepGoing = freshSym(NoPosition, BooleanClass.tpe, "keepGoing") setFlag MUTABLE lazy val matchRes = freshSym(NoPosition, AnyClass.tpe, "matchRes") setFlag MUTABLE - def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, hasDefault: Boolean) = { + def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, catchAll: Option[Tree => Tree]) = { matchRes.info = if (resTp ne NoType) resTp.widen else AnyClass.tpe // we don't always know resTp, and it might be AnyVal, in which case we can't assign NULL if (dontStore(resTp)) matchRes resetFlag MUTABLE // don't assign to Unit-typed var's, in fact, make it a val -- conveniently also works around SI-5245 BLOCK( VAL(zeroSym) === REF(NoneModule), // TODO: can we just get rid of explicitly emitted zero? don't know how to do that as a local rewrite... - VAL(scrutSym) === scrut, // reuse the symbol of the function's argument to avoid creating a fresh one and substituting it for scrutSym in `matcher` -- the owner structure is repaired by fixerUpper + VAL(scrutSym) === scrut, VAL(matchRes) === mkZero(matchRes.info), // must cast to deal with GADT typing, hence the private mkZero above VAL(keepGoing) === TRUE, matcher, - if(hasDefault) REF(matchRes) - else (IF (REF(keepGoing)) THEN MATCHERROR(REF(scrutSym)) ELSE REF(matchRes)) + catchAll map { catchAllGen => (IF (REF(keepGoing)) THEN catchAllGen(REF(scrutSym)) ELSE REF(matchRes)) } getOrElse REF(matchRes) ) } @@ -1576,7 +1702,7 @@ class Foo(x: Other) { x._1 } // no error in this order def one(res: Tree, bodyPt: Type, matchPt: Type): Tree = { BLOCK( if (dontStore(matchPt)) res // runOrElse hasn't been called yet, so matchRes.isMutable is irrelevant, also, tp may be a subtype of resTp used in runOrElse... - else (REF(matchRes) === res), // _asInstanceOf(res, tp.widen, force = true) + else (REF(matchRes) === res), // TODO -- move this statement after the keepGoing-assignment and mark the matchRes-assignment as a tailposition somehow -- _asInstanceOf(res, tp.widen, force = true) REF(keepGoing) === FALSE, zero // to have a nice lub for lubs -- otherwise we'll get a boxed unit here -- TODO: get rid of all those dangling else zero's ) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 5411ea1788..23fc30b9fb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4250,6 +4250,11 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { block1 = adapt(block1, mode, owntype) catches1 = catches1 map (adaptCase(_, owntype)) } + + if(!isPastTyper && opt.virtPatmat) { + catches1 = (MatchTranslator(this)).translateTry(catches1, owntype, tree.pos) + } + treeCopy.Try(tree, block1, catches1, finalizer1) setType owntype case Throw(expr) => diff --git a/test/files/run/virtpatmat_switch.scala b/test/files/run/virtpatmat_switch.scala index 2e2c31e8e5..1329c19d0f 100644 --- a/test/files/run/virtpatmat_switch.scala +++ b/test/files/run/virtpatmat_switch.scala @@ -14,9 +14,15 @@ object Test extends App { case 'b' => "got b" case _ => "got some letter" } + + def byteSwitch(x: Byte) = x match { + case 'a' => "got a" + case 'b' => "got b" + case _ => "got some letter" + } println(charSwitch('a')) - println(charSwitch('b')) + println(byteSwitch('b')) println(charSwitch('z')) def implicitDefault(x: Int) = x match { diff --git a/test/files/run/virtpatmat_try.check b/test/files/run/virtpatmat_try.check new file mode 100644 index 0000000000..80ebbf494a --- /dev/null +++ b/test/files/run/virtpatmat_try.check @@ -0,0 +1,2 @@ +meh +B diff --git a/test/files/run/virtpatmat_try.flags b/test/files/run/virtpatmat_try.flags new file mode 100644 index 0000000000..9769db9257 --- /dev/null +++ b/test/files/run/virtpatmat_try.flags @@ -0,0 +1 @@ + -Yvirtpatmat -Xexperimental diff --git a/test/files/run/virtpatmat_try.scala b/test/files/run/virtpatmat_try.scala new file mode 100644 index 0000000000..46e67cb72e --- /dev/null +++ b/test/files/run/virtpatmat_try.scala @@ -0,0 +1,47 @@ +object Test extends App { + case class A(val x: String) extends Throwable + class B extends Exception { override def toString = "B" } + def bla = 0 + + try { + throw new A("meh") + } catch { // this should emit a "catch-switch" + case y: A => println(y.x) + case (_ : A | _ : B) => println("B") + case _ => println("other") + } + + try { + throw new B() + } catch { // case classes and alternative flattening aren't supported yet, but could be in principle + // case A(x) => println(x) + case y: A => println(y.x) + case x@((_ : A) | (_ : B)) => println(x) + case _ => println("other") + } + + def simpleTry { + try { + bla + } catch { + case x: Exception if x.getMessage == "test" => println("first case " + x) + case x: Exception => println("second case " + x) + } + } + + def typedWildcardTry { + try { bla } catch { case _: ClassCastException => bla } + } + + def wildcardTry { + try { bla } catch { case _ => bla } + } + + def tryPlusFinally { + try { bla } finally { println("finally") } + } + + def catchAndPassToLambda { + try { bla } catch { case ex: Exception => val f = () => ex } + } +} \ No newline at end of file -- cgit v1.2.3