From 994df2722255a0d0b4ef929efdebfab60661f4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 22 Aug 2018 13:38:05 +0200 Subject: [PATCH 1/2] Give the Method flag to the label-defs produced by TailRec. This is consistent with those produced by the current pattern matcher. --- compiler/src/dotty/tools/dotc/transform/TailRec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index f8cbbb033391..fb8160c5a146 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -70,7 +70,7 @@ class TailRec extends MiniPhase with FullParameterization { override def runsAfter = Set(ShortcutImplicits.name) // Replaces non-tail calls by tail calls - final val labelFlags = Flags.Synthetic | Flags.Label + final val labelFlags = Flags.Synthetic | Flags.Label | Flags.Method /** Symbols of methods that have @tailrec annotatios inside */ private val methodsWithInnerAnnots = new collection.mutable.HashSet[Symbol]() From 4c530086bdb785e4ff7e55770ffdc84468d6ff9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 22 Aug 2018 14:24:03 +0200 Subject: [PATCH 2/2] Introduce Labeled blocks, and use them in PatternMatcher. A Labeled block is an expression tree of the form label[T]: { expr } where `expr` must conform to the type `T`. In addition, within `expr` (but nowhere else), return from the label is allowed: return[label] e where `e` must conform to the type `T` as well. If execution of `expr` completes normally (rather than throwing an exception or returning, etc.), then the result of evaluating the `Labeled` block is the result of `expr`. If a `return[label] e` is reached, the execution of `expr` is interrupted, and the result of evaluating the `Labeled` block is the result of evaluating the argument `e`. Implementation-wise, a `Labeled` block is represented as a `Tree` with the shape: Labeled(Bind(labelName), expr) where the `Bind` nodes holds the definition of the label symbol. That symbol is a term symbol with the flag `Label` (but not `Method`, unlike symbols for label-defs) and whose `info` is the result type `T` of the labeled block. We use those new `Labeled` blocks in `PatternMatcher`, instead of label-defs. This is the first step towards completely removing label-defs from the compiler. This commit structurally fixes a few issues: * It fixes #1313 through the `mergeTests` optimization. * It fixes #4563 because Labeled blocks are erasure-friendly. * It does a big step towards fixing the upstream test t10387: the compiler can get to the back-end on that test, but it produces too much bytecode for a single JVM method. We do add a sister test t10387b which works because optimizations can kick in. --- .../backend/jvm/DottyBackendInterface.scala | 10 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 18 +- compiler/src/dotty/tools/dotc/ast/tpd.scala | 9 + compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../src/dotty/tools/dotc/core/NameKinds.scala | 8 +- .../dotty/tools/dotc/core/TypeErasure.scala | 1 + .../tools/dotc/printing/RefinedPrinter.scala | 8 +- .../tools/dotc/transform/MegaPhase.scala | 23 + .../dotc/transform/NonLocalReturns.scala | 2 +- .../tools/dotc/transform/PatternMatcher.scala | 676 ++++++++---------- .../tools/dotc/transform/TreeChecker.scala | 18 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 3 + .../src/dotty/tools/dotc/typer/Typer.scala | 7 + scala-backend | 2 +- .../pos-special/fatal-warnings/switches.scala | 33 + tests/pos/t10387b.scala | 269 +++++++ tests/run/i4563.check | 3 + tests/run/i4563.scala | 19 + tests/run/reducable.scala | 55 +- 19 files changed, 755 insertions(+), 410 deletions(-) create mode 100644 tests/pos-special/fatal-warnings/switches.scala create mode 100644 tests/pos/t10387b.scala create mode 100644 tests/run/i4563.check create mode 100644 tests/run/i4563.scala diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 606c8207cee8..fb5fb35e49e9 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -61,6 +61,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma type If = tpd.If type ValDef = tpd.ValDef type Throw = tpd.Apply + type Labeled = tpd.Labeled type Return = tpd.Return type Block = tpd.Block type Typed = tpd.Typed @@ -193,6 +194,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma implicit val LabelDefTag: ClassTag[LabelDef] = ClassTag[LabelDef](classOf[LabelDef]) implicit val ValDefTag: ClassTag[ValDef] = ClassTag[ValDef](classOf[ValDef]) implicit val ThrowTag: ClassTag[Throw] = ClassTag[Throw](classOf[Throw]) + implicit val LabeledTag: ClassTag[Labeled] = ClassTag[Labeled](classOf[Labeled]) implicit val ReturnTag: ClassTag[Return] = ClassTag[Return](classOf[Return]) implicit val LiteralTag: ClassTag[Literal] = ClassTag[Literal](classOf[Literal]) implicit val BlockTag: ClassTag[Block] = ClassTag[Block](classOf[Block]) @@ -1076,8 +1078,14 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def apply(s: Symbol): This = tpd.This(s.asClass) } + object Labeled extends LabeledDeconstructor { + def _1: Bind = field.bind + def _2: Tree = field.expr + } + object Return extends ReturnDeconstructor { - def get = field.expr + def _1: Tree = field.expr + def _2: Symbol = if (field.from.symbol.isLabel) field.from.symbol else NoSymbol } object Ident extends IdentDeconstructor { diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index cf3b1359cfe6..f015b314e15b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -524,8 +524,15 @@ object Trees { type ThisTree[-T >: Untyped] = CaseDef[T] } + /** label[tpt]: { expr } */ + case class Labeled[-T >: Untyped] private[ast] (bind: Bind[T], expr: Tree[T]) + extends NameTree[T] { + type ThisTree[-T >: Untyped] = Labeled[T] + def name: Name = bind.name + } + /** return expr - * where `from` refers to the method from which the return takes place + * where `from` refers to the method or label from which the return takes place * After program transformations this is not necessarily the enclosing method, because * closures can intervene. */ @@ -886,6 +893,7 @@ object Trees { type Closure = Trees.Closure[T] type Match = Trees.Match[T] type CaseDef = Trees.CaseDef[T] + type Labeled = Trees.Labeled[T] type Return = Trees.Return[T] type Try = Trees.Try[T] type SeqLiteral = Trees.SeqLiteral[T] @@ -1028,6 +1036,10 @@ object Trees { case tree: CaseDef if (pat eq tree.pat) && (guard eq tree.guard) && (body eq tree.body) => tree case _ => finalize(tree, untpd.CaseDef(pat, guard, body)) } + def Labeled(tree: Tree)(bind: Bind, expr: Tree)(implicit ctx: Context): Labeled = tree match { + case tree: Labeled if (bind eq tree.bind) && (expr eq tree.expr) => tree + case _ => finalize(tree, untpd.Labeled(bind, expr)) + } def Return(tree: Tree)(expr: Tree, from: Tree)(implicit ctx: Context): Return = tree match { case tree: Return if (expr eq tree.expr) && (from eq tree.from) => tree case _ => finalize(tree, untpd.Return(expr, from)) @@ -1202,6 +1214,8 @@ object Trees { cpy.Match(tree)(transform(selector), transformSub(cases)) case CaseDef(pat, guard, body) => cpy.CaseDef(tree)(transform(pat), transform(guard), transform(body)) + case Labeled(bind, expr) => + cpy.Labeled(tree)(transformSub(bind), transform(expr)) case Return(expr, from) => cpy.Return(tree)(transform(expr), transformSub(from)) case Try(block, cases, finalizer) => @@ -1334,6 +1348,8 @@ object Trees { this(this(x, selector), cases) case CaseDef(pat, guard, body) => this(this(this(x, pat), guard), body) + case Labeled(bind, expr) => + this(this(x, bind), expr) case Return(expr, from) => this(this(x, expr), from) case Try(block, handler, finalizer) => diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index f2e4b3e14c84..ec0884e6ee99 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -120,6 +120,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Match(selector: Tree, cases: List[CaseDef])(implicit ctx: Context): Match = ta.assignType(untpd.Match(selector, cases), cases) + def Labeled(bind: Bind, expr: Tree)(implicit ctx: Context): Labeled = + ta.assignType(untpd.Labeled(bind, expr)) + + def Labeled(sym: TermSymbol, expr: Tree)(implicit ctx: Context): Labeled = + Labeled(Bind(sym, EmptyTree), expr) + def Return(expr: Tree, from: Tree)(implicit ctx: Context): Return = ta.assignType(untpd.Return(expr, from)) @@ -594,6 +600,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } + override def Labeled(tree: Tree)(bind: Bind, expr: Tree)(implicit ctx: Context): Labeled = + ta.assignType(untpd.cpy.Labeled(tree)(bind, expr)) + override def Return(tree: Tree)(expr: Tree, from: Tree)(implicit ctx: Context): Return = ta.assignType(untpd.cpy.Return(tree)(expr, from)) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 878d307dcd66..a789e3748e06 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -276,6 +276,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Closure(env: List[Tree], meth: Tree, tpt: Tree): Closure = new Closure(env, meth, tpt) def Match(selector: Tree, cases: List[CaseDef]): Match = new Match(selector, cases) def CaseDef(pat: Tree, guard: Tree, body: Tree): CaseDef = new CaseDef(pat, guard, body) + def Labeled(bind: Bind, expr: Tree): Labeled = new Labeled(bind, expr) def Return(expr: Tree, from: Tree): Return = new Return(expr, from) def Try(expr: Tree, cases: List[CaseDef], finalizer: Tree): Try = new Try(expr, cases, finalizer) def SeqLiteral(elems: List[Tree], elemtpt: Tree): SeqLiteral = new SeqLiteral(elems, elemtpt) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 2c69f6339f1c..4cdae5d17553 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -314,12 +314,8 @@ object NameKinds { /** Kinds of unique names generated by the pattern matcher */ val PatMatStdBinderName = new UniqueNameKind("x") - val PatMatPiName = new UniqueNameKind("pi") // FIXME: explain what this is - val PatMatPName = new UniqueNameKind("p") // FIXME: explain what this is - val PatMatOName = new UniqueNameKind("o") // FIXME: explain what this is - val PatMatCaseName = new UniqueNameKind("case") - val PatMatMatchFailName = new UniqueNameKind("matchFail") - val PatMatSelectorName = new UniqueNameKind("selector") + val PatMatAltsName = new UniqueNameKind("matchAlts") + val PatMatResultName = new UniqueNameKind("matchResult") val LocalOptInlineLocalObj = new UniqueNameKind("ilo") diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 36cb45233771..be59b0eb8849 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -178,6 +178,7 @@ object TypeErasure { if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType]) else if (sym.isAbstractType) TypeAlias(WildcardType) else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx)) + else if (sym.is(Label, butNot = Method)) erase.eraseResult(sym.info)(erasureCtx) else erase.eraseInfo(tp, sym)(erasureCtx) match { case einfo: MethodType => if (sym.isGetter && einfo.resultType.isRef(defn.UnitClass)) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 7ae4192c7bd6..5f42fa346f27 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -372,8 +372,14 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else changePrec(GlobalPrec) { toText(sel) ~ keywordStr(" match ") ~ blockText(cases) } case CaseDef(pat, guard, body) => keywordStr("case ") ~ inPattern(toText(pat)) ~ optText(guard)(keywordStr(" if ") ~ _) ~ " => " ~ caseBlockText(body) + case Labeled(bind, expr) => + changePrec(GlobalPrec) { toText(bind.name) ~ keywordStr("[") ~ toText(bind.symbol.info) ~ keywordStr("]: ") ~ toText(expr) } case Return(expr, from) => - changePrec(GlobalPrec) { keywordStr("return") ~ optText(expr)(" " ~ _) } + val sym = from.symbol + if (sym.is(Label)) + changePrec(GlobalPrec) { keywordStr("return[") ~ toText(sym.name) ~ keywordStr("]") ~ optText(expr)(" " ~ _) } + else + changePrec(GlobalPrec) { keywordStr("return") ~ optText(expr)(" " ~ _) } case Try(expr, cases, finalizer) => changePrec(GlobalPrec) { keywordStr("try ") ~ toText(expr) ~ optText(cases)(keywordStr(" catch ") ~ _) ~ optText(finalizer)(keywordStr(" finally ") ~ _) diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index 37965cdf0872..e361081c82aa 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -66,6 +66,7 @@ object MegaPhase { def prepareForClosure(tree: Closure)(implicit ctx: Context) = ctx def prepareForMatch(tree: Match)(implicit ctx: Context) = ctx def prepareForCaseDef(tree: CaseDef)(implicit ctx: Context) = ctx + def prepareForLabeled(tree: Labeled)(implicit ctx: Context) = ctx def prepareForReturn(tree: Return)(implicit ctx: Context) = ctx def prepareForTry(tree: Try)(implicit ctx: Context) = ctx def prepareForSeqLiteral(tree: SeqLiteral)(implicit ctx: Context) = ctx @@ -98,6 +99,7 @@ object MegaPhase { def transformClosure(tree: Closure)(implicit ctx: Context): Tree = tree def transformMatch(tree: Match)(implicit ctx: Context): Tree = tree def transformCaseDef(tree: CaseDef)(implicit ctx: Context): Tree = tree + def transformLabeled(tree: Labeled)(implicit ctx: Context): Tree = tree def transformReturn(tree: Return)(implicit ctx: Context): Tree = tree def transformTry(tree: Try)(implicit ctx: Context): Tree = tree def transformSeqLiteral(tree: SeqLiteral)(implicit ctx: Context): Tree = tree @@ -165,6 +167,7 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { case tree: ValDef => goValDef(tree, start) case tree: DefDef => goDefDef(tree, start) case tree: TypeDef => goTypeDef(tree, start) + case tree: Labeled => goLabeled(tree, start) case tree: Bind => goBind(tree, start) case _ => goOther(tree, start) } @@ -249,6 +252,11 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { implicit val ctx = prepTypeDef(tree, start)(outerCtx) val rhs = transformTree(tree.rhs, start)(localContext) goTypeDef(cpy.TypeDef(tree)(tree.name, rhs), start) + case tree: Labeled => + implicit val ctx = prepLabeled(tree, start)(outerCtx) + val bind = transformTree(tree.bind, start).asInstanceOf[Bind] + val expr = transformTree(tree.expr, start) + goLabeled(cpy.Labeled(tree)(bind, expr), start) case tree: Bind => implicit val ctx = prepBind(tree, start)(outerCtx) val body = transformTree(tree.body, start) @@ -745,6 +753,21 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { } } + def prepLabeled(tree: Labeled, start: Int)(implicit ctx: Context): Context = { + val phase = nxReturnPrepPhase(start) + if (phase == null) ctx + else prepLabeled(tree, phase.idxInGroup + 1)(phase.prepareForLabeled(tree)) + } + + def goLabeled(tree: Labeled, start: Int)(implicit ctx: Context): Tree = { + val phase = nxReturnTransPhase(start) + if (phase == null) tree + else phase.transformLabeled(tree)(ctx) match { + case tree1: Labeled => goLabeled(tree1, phase.idxInGroup + 1) + case tree1 => transformNode(tree1, phase.idxInGroup + 1) + } + } + def prepReturn(tree: Return, start: Int)(implicit ctx: Context): Context = { val phase = nxReturnPrepPhase(start) if (phase == null) ctx diff --git a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala index c93bcd9639b9..80b6944625f8 100644 --- a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala +++ b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala @@ -11,7 +11,7 @@ import collection.mutable object NonLocalReturns { import ast.tpd._ def isNonLocalReturn(ret: Return)(implicit ctx: Context) = - ret.from.symbol != ctx.owner.enclosingMethod || ctx.owner.is(Lazy) + !ret.from.symbol.is(Label) && (ret.from.symbol != ctx.owner.enclosingMethod || ctx.owner.is(Lazy)) } /** Implement non-local returns using NonLocalReturnControl exceptions. diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index ebaca5b626b1..d4a9331db261 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -1,6 +1,7 @@ package dotty.tools.dotc package transform +import scala.annotation.tailrec import core._ import MegaPhase._ import collection.mutable @@ -12,7 +13,7 @@ import SymUtils._ import Flags._, Constants._ import Decorators._ import patmat.Space -import NameKinds.{UniqueNameKind, PatMatStdBinderName, PatMatCaseName} +import NameKinds.{UniqueNameKind, PatMatStdBinderName, PatMatAltsName, PatMatResultName} import config.Printers.patmatch import reporting.diagnostic.messages._ @@ -52,8 +53,7 @@ object PatternMatcher { /** Was symbol generated by pattern matcher? */ def isPatmatGenerated(sym: Symbol)(implicit ctx: Context): Boolean = - sym.is(Synthetic) && - (sym.name.is(PatMatStdBinderName) || sym.name.is(PatMatCaseName)) + sym.is(Synthetic) && sym.name.is(PatMatStdBinderName) /** The pattern matching translator. * Its general structure is a pipeline: @@ -67,20 +67,22 @@ object PatternMatcher { * - emit the translated tree, using methods `emit`, `collectSwitchCases`, * `emitSwitchCases`, and `emitCondition`. * - * A plan represents the underlying decision graph. It consists - * of tests, let and label bindings, calls to labels and code blocks. - * It's represented by its own data type. Plans are optimized by - * inlining, hoisting, and the elimination of redundant tests and dead code. + * A plan represents the underlying decision graph. It consists of tests, + * let bindings, labeled blocks, return from said labeled blocks and code blocks. + * It's represented by its own data type. Plans are optimized by merging common + * tests and eliminating dead code. */ class Translator(resultType: Type, thisPhase: MiniPhase)(implicit ctx: Context) { // ------- Bindings for variables and labels --------------------- + private val resultLabel = + ctx.newSymbol(ctx.owner, PatMatResultName.fresh(), Synthetic | Label, resultType) + /** A map from variable symbols to their defining trees * and from labels to their defining plans */ private val initializer = newMutableSymbolMap[Tree] - private val labelled = newMutableSymbolMap[Plan] private def newVar(rhs: Tree, flags: FlagSet): TermSymbol = ctx.newSymbol(ctx.owner, PatMatStdBinderName.fresh(), Synthetic | Case | flags, @@ -94,12 +96,11 @@ object PatternMatcher { LetPlan(vble, body(vble)) } - /** The plan `let l = labelled in body(l)` where `l` is a fresh label */ - private def labelAbstract(labeld: Plan)(body: (=> Plan) => Plan): Plan = { - val label = ctx.newSymbol(ctx.owner, PatMatCaseName.fresh(), Synthetic | Label | Method, - MethodType(Nil, resultType)) - labelled(label) = labeld - LabelledPlan(label, body(CallPlan(label, Nil)), Nil) + /** The plan `l: { expr(l) }` where `l` is a fresh label */ + private def altsLabeledAbstract(expr: (=> Plan) => Plan): Plan = { + val label = ctx.newSymbol(ctx.owner, PatMatAltsName.fresh(), Synthetic | Label, + defn.UnitType) + LabeledPlan(label, expr(ReturnPlan(label))) } /** Test whether a type refers to a pattern-generated variable */ @@ -130,7 +131,7 @@ object PatternMatcher { sealed abstract class Plan { val id = nxId; nxId += 1 } case class TestPlan(test: Test, var scrutinee: Tree, pos: Position, - var onSuccess: Plan, var onFailure: Plan) extends Plan { + var onSuccess: Plan) extends Plan { override def equals(that: Any) = that match { case that: TestPlan => this.scrutinee === that.scrutinee && this.test == that.test case _ => false @@ -139,14 +140,14 @@ object PatternMatcher { } case class LetPlan(sym: TermSymbol, var body: Plan) extends Plan - case class LabelledPlan(sym: TermSymbol, var body: Plan, var params: List[TermSymbol]) extends Plan - case class CodePlan(var tree: Tree) extends Plan - case class CallPlan(label: TermSymbol, - var args: List[(/*formal*/TermSymbol, /*actual*/TermSymbol)]) extends Plan + case class LabeledPlan(sym: TermSymbol, var expr: Plan) extends Plan + case class ReturnPlan(var label: TermSymbol) extends Plan + case class SeqPlan(var head: Plan, var tail: Plan) extends Plan + case class ResultPlan(var tree: Tree) extends Plan object TestPlan { - def apply(test: Test, sym: Symbol, pos: Position, ons: Plan, onf: Plan): TestPlan = - TestPlan(test, ref(sym), pos, ons, onf) + def apply(test: Test, sym: Symbol, pos: Position, ons: Plan): TestPlan = + TestPlan(test, ref(sym), pos, ons) } /** The different kinds of tests */ @@ -211,7 +212,7 @@ object PatternMatcher { } /** Plan for matching `scrutinee` symbol against `tree` pattern */ - private def patternPlan(scrutinee: Symbol, tree: Tree, onSuccess: Plan, onFailure: Plan): Plan = { + private def patternPlan(scrutinee: Symbol, tree: Tree, onSuccess: Plan): Plan = { /** Plan for matching `selectors` against argument patterns `args` */ def matchArgsPlan(selectors: List[Tree], args: List[Tree], onSuccess: Plan): Plan = { @@ -240,7 +241,7 @@ object PatternMatcher { args match { case arg :: args1 => val sym :: syms1 = syms - patternPlan(sym, arg, matchArgsPatternPlan(args1, syms1), onFailure) + patternPlan(sym, arg, matchArgsPatternPlan(args1, syms1)) case Nil => assert(syms.isEmpty) onSuccess @@ -255,7 +256,7 @@ object PatternMatcher { val selectors = args.indices.toList.map(idx => ref(seqSym).select(nme.apply).appliedTo(Literal(Constant(idx)))) TestPlan(LengthTest(args.length, exact), seqSym, seqSym.pos, - matchArgsPlan(selectors, args, onSuccess), onFailure) + matchArgsPlan(selectors, args, onSuccess)) } /** Plan for matching the sequence in `getResult` against sequence elements @@ -265,13 +266,13 @@ object PatternMatcher { case Some(VarArgPattern(arg)) => val matchRemaining = if (args.length == 1) - patternPlan(getResult, arg, onSuccess, onFailure) + patternPlan(getResult, arg, onSuccess) else { val dropped = ref(getResult) .select(defn.Seq_drop.matchingMember(getResult.info)) .appliedTo(Literal(Constant(args.length - 1))) letAbstract(dropped) { droppedResult => - patternPlan(droppedResult, arg, onSuccess, onFailure) + patternPlan(droppedResult, arg, onSuccess) } } matchElemsPlan(getResult, args.init, exact = false, matchRemaining) @@ -290,7 +291,7 @@ object PatternMatcher { if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length) matchArgsPlan(caseAccessors.map(ref(scrutinee).select(_)), args, onSuccess) else if (unapp.tpe.widenSingleton.isRef(defn.BooleanClass)) - TestPlan(GuardTest, unapp, unapp.pos, onSuccess, onFailure) + TestPlan(GuardTest, unapp, unapp.pos, onSuccess) else { letAbstract(unapp) { unappResult => val isUnapplySeq = unapp.symbol.name == nme.unapplySeq @@ -313,7 +314,7 @@ object PatternMatcher { matchArgsPlan(selectors, args, onSuccess) } } - TestPlan(NonEmptyTest, unappResult, unapp.pos, argsPlan, onFailure) + TestPlan(NonEmptyTest, unappResult, unapp.pos, argsPlan) } } } @@ -325,54 +326,58 @@ object PatternMatcher { TestPlan(TypeTest(tpt), scrutinee, tree.pos, letAbstract(ref(scrutinee).asInstance(tpt.tpe)) { casted => nonNull += casted - patternPlan(casted, pat, onSuccess, onFailure) - }, - onFailure) + patternPlan(casted, pat, onSuccess) + }) case UnApply(extractor, implicits, args) => val mt @ MethodType(_) = extractor.tpe.widen var unapp = extractor.appliedTo(ref(scrutinee).ensureConforms(mt.paramInfos.head)) if (implicits.nonEmpty) unapp = unapp.appliedToArgs(implicits) val unappPlan = unapplyPlan(unapp, args) if (scrutinee.info.isNotNull || nonNull(scrutinee)) unappPlan - else TestPlan(NonNullTest, scrutinee, tree.pos, unappPlan, onFailure) + else TestPlan(NonNullTest, scrutinee, tree.pos, unappPlan) case Bind(name, body) => - if (name == nme.WILDCARD) patternPlan(scrutinee, body, onSuccess, onFailure) + if (name == nme.WILDCARD) patternPlan(scrutinee, body, onSuccess) else { // The type of `name` may refer to val in `body`, therefore should come after `body` val bound = tree.symbol.asTerm initializer(bound) = ref(scrutinee) - patternPlan(scrutinee, body, LetPlan(bound, onSuccess), onFailure) + patternPlan(scrutinee, body, LetPlan(bound, onSuccess)) } case Alternative(alts) => - labelAbstract(onSuccess) { ons => - (alts :\ onFailure) { (alt, onf) => - labelAbstract(onf) { onf1 => - patternPlan(scrutinee, alt, ons, onf1) - } - } + altsLabeledAbstract { onf => + SeqPlan( + altsLabeledAbstract { ons => + alts.foldRight(onf) { (alt, next) => + SeqPlan(patternPlan(scrutinee, alt, ons), next) + } + }, + onSuccess + ) } case WildcardPattern() => onSuccess case SeqLiteral(pats, _) => matchElemsPlan(scrutinee, pats, exact = true, onSuccess) case _ => - TestPlan(EqualTest(tree), scrutinee, tree.pos, onSuccess, onFailure) + TestPlan(EqualTest(tree), scrutinee, tree.pos, onSuccess) } } - private def caseDefPlan(scrutinee: Symbol, cdef: CaseDef, onFailure: Plan): Plan = - labelAbstract(onFailure) { onf => - var onSuccess: Plan = CodePlan(cdef.body) - if (!cdef.guard.isEmpty) - onSuccess = TestPlan(GuardTest, cdef.guard, cdef.guard.pos, onSuccess, onf) - patternPlan(scrutinee, cdef.pat, onSuccess, onf) - } + private def caseDefPlan(scrutinee: Symbol, cdef: CaseDef): Plan = { + var onSuccess: Plan = ResultPlan(cdef.body) + if (!cdef.guard.isEmpty) + onSuccess = TestPlan(GuardTest, cdef.guard, cdef.guard.pos, onSuccess) + patternPlan(scrutinee, cdef.pat, onSuccess) + } - private def matchPlan(tree: Match): Plan = + private def matchPlan(tree: Match): Plan = { letAbstract(tree.selector) { scrutinee => - val matchError: Plan = CodePlan(Throw(New(defn.MatchErrorType, ref(scrutinee) :: Nil))) - (tree.cases :\ matchError)(caseDefPlan(scrutinee, _, _)) + val matchError: Plan = ResultPlan(Throw(New(defn.MatchErrorType, ref(scrutinee) :: Nil))) + tree.cases.foldRight(matchError) { (cdef, next) => + SeqPlan(caseDefPlan(scrutinee, cdef), next) + } } + } // ----- Optimizing plans --------------- @@ -385,7 +390,6 @@ object PatternMatcher { def apply(plan: TestPlan): Plan = { plan.scrutinee = apply(plan.scrutinee) plan.onSuccess = apply(plan.onSuccess) - plan.onFailure = apply(plan.onFailure) plan } def apply(plan: LetPlan): Plan = { @@ -393,18 +397,23 @@ object PatternMatcher { initializer(plan.sym) = apply(initializer(plan.sym)) plan } - def apply(plan: LabelledPlan): Plan = { - plan.body = apply(plan.body) - labelled(plan.sym) = apply(labelled(plan.sym)) + def apply(plan: LabeledPlan): Plan = { + plan.expr = apply(plan.expr) + plan + } + def apply(plan: ReturnPlan): Plan = plan + def apply(plan: SeqPlan): Plan = { + plan.head = apply(plan.head) + plan.tail = apply(plan.tail) plan } - def apply(plan: CallPlan): Plan = plan def apply(plan: Plan): Plan = plan match { case plan: TestPlan => apply(plan) case plan: LetPlan => apply(plan) - case plan: LabelledPlan => apply(plan) - case plan: CallPlan => apply(plan) - case plan: CodePlan => plan + case plan: LabeledPlan => apply(plan) + case plan: ReturnPlan => apply(plan) + case plan: SeqPlan => apply(plan) + case plan: ResultPlan => plan } } @@ -417,12 +426,11 @@ object PatternMatcher { /** Reference counts for all labels */ private def labelRefCount(plan: Plan): collection.Map[Symbol, Int] = { object refCounter extends RefCounter { - override def apply(plan: LabelledPlan): Plan = { - apply(plan.body) - if (count(plan.sym) != 0) apply(labelled(plan.sym)) + override def apply(plan: LabeledPlan): Plan = { + apply(plan.expr) plan } - override def apply(plan: CallPlan): Plan = { + override def apply(plan: ReturnPlan): Plan = { count(plan.label) += 1 plan } @@ -449,267 +457,111 @@ object PatternMatcher { apply(initializer(plan.sym)) plan } - override def apply(plan: LabelledPlan): Plan = { - apply(labelled(plan.sym)) - apply(plan.body) - plan - } - override def apply(plan: CallPlan): Plan = { - for ((formal, actual) <- plan.args) - if (count(formal) != 0) count(actual) += 1 - plan - } } refCounter(plan) refCounter.count } - /** Rewrite everywhere + /** Merge identical consecutive tests. * - * if C then (let L = B in E1) else E2 - * --> - * let L = B in if C then E1 else E2 + * When we have the following shape: * - * if C then E1 else (let L = B in E2) - * --> - * let L = B in if C then E1 else E2 + * if (testA) plan1 + * if (testA) plan2 + * nextPlan? * - * let L1 = (let L2 = B2 in B1) in E - * --> - * let L2 = B2 in let L1 = B1 in E - */ - object hoistLabels extends PlanTransform { - override def apply(plan: TestPlan): Plan = - plan.onSuccess match { - case lp @ LabelledPlan(sym, body, _) => - plan.onSuccess = body - lp.body = plan - apply(lp) - case _ => - plan.onFailure match { - case lp @ LabelledPlan(sym, body, _) => - plan.onFailure = body - lp.body = plan - apply(lp) - case _ => - super.apply(plan) - } - } - override def apply(plan: LabelledPlan): Plan = - labelled(plan.sym) match { - case plan1: LabelledPlan => - labelled(plan.sym) = plan1.body - plan1.body = plan - apply(plan1) - case _ => - super.apply(plan) - } - } - - /** Eliminate tests that are redundant (known to be true or false). - * Two parts: + * transform it to * - * - If we know at some point that a test is true or false skip it and continue - * diretcly with the test's onSuccess or onFailure continuation. - * - If a label of a call points to a test that is known to be true or false - * at the point of call, let the label point instead to the test's onSuccess - * or onFailure continuation. + * if (testA) { + * plan1 + * plan2 + * } + * nextPlan? * - * We use some tricks to identify a let pointing to an unapply and the - * NonEmptyTest that follows it as a single `UnappTest` test. + * Similarly, when we have equivalent let bindings: + * + * let x1 = rhs1 in plan1 + * let x2 = rhs2 in plan2 + * nextPlan? + * + * and rhs1 and rhs2 are equivalent, transform it to + * + * let x1 = rhs1 in { + * plan1 + * plan2[x1/x2] + * } + * + * where plan2[x1/x2] means substituting x1 for x2 in plan2. + * + * There are some tricks to "ignore" non-patmat-generated let bindings, i.e., + * captures written in the source code, while identifying common subplans. */ - def elimRedundantTests(plan: Plan): Plan = { - type SeenTests = Map[TestPlan, Boolean] // Map from tests to their outcomes - - def isUnapply(sym: Symbol) = sym.name == nme.unapply || sym.name == nme.unapplySeq - - /** A locally used test value that represents combos of - * - * let x = X.unapply(...) in if !x.isEmpty then ... else ... - */ - case object UnappTest extends Test - - /** If `plan` is the NonEmptyTest part of an unapply, the corresponding UnappTest - * otherwise the original plan - */ - def normalize(plan: TestPlan): TestPlan = plan.scrutinee match { - case id: Ident - if plan.test == NonEmptyTest && - isPatmatGenerated(id.symbol) && - isUnapply(initializer(id.symbol).symbol) => - TestPlan(UnappTest, initializer(id.symbol), plan.pos, plan.onSuccess, plan.onFailure) - case _ => - plan + def mergeTests(plan: Plan): Plan = { + class SubstituteIdent(from: TermSymbol, to: TermSymbol) extends PlanTransform { + override val treeMap = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context) = tree match { + case tree: Ident if tree.symbol == from => ref(to) + case _ => super.transform(tree) + } + } } - /** Extractor for Let/NonEmptyTest combos that represent unapplies */ - object UnappTestPlan { - def unapply(plan: Plan): Option[TestPlan] = plan match { - case LetPlan(sym, body: TestPlan) => - val RHS = initializer(sym) - normalize(body) match { - case normPlan @ TestPlan(UnappTest, RHS, _, _, _) => Some(normPlan) - case _ => None + class MergeTests extends PlanTransform { + override def apply(plan: SeqPlan): Plan = { + def tryMerge(plan1: Plan, plan2: Plan): Option[Plan] = { + def skipNonPatmatGenedLets(plan: Plan): Plan = plan match { + case LetPlan(sym, body) if !isPatmatGenerated(sym) => + skipNonPatmatGenedLets(body) + case _ => + plan } - case _ => None - } - } - def intersect(tests1: SeenTests, tests2: SeenTests) = - tests1.filter { case(test, outcome) => tests2.get(test) == Some(outcome) } + def transferNonPatmatGenedLets(originalPlan: Plan, newPlan: Plan): Plan = originalPlan match { + case originalPlan: LetPlan if !isPatmatGenerated(originalPlan.sym) => + originalPlan.body = transferNonPatmatGenedLets(originalPlan.body, newPlan) + originalPlan + case _ => + newPlan + } - /** The tests with known outcomes valid at entry to label */ - val seenAtLabel = newMutableSymbolMap[SeenTests] + (skipNonPatmatGenedLets(plan1), skipNonPatmatGenedLets(plan2)) match { + case (testPlan1: TestPlan, testPlan2: TestPlan) if testPlan1 == testPlan2 => + /* Because testPlan2 is the same as testPlan1, it cannot possibly refer to + * the symbols defined by any of the skipped lets. + */ + testPlan1.onSuccess = SeqPlan(testPlan1.onSuccess, + transferNonPatmatGenedLets(plan2, testPlan2.onSuccess)) + Some(plan1) // that's the original plan1, on purpose + + case (letPlan1: LetPlan, letPlan2: LetPlan) if initializer(letPlan1.sym) === initializer(letPlan2.sym) => + // By construction, letPlan1.sym and letPlan2.sym are patmat-generated + val newPlan2Body = new SubstituteIdent(letPlan2.sym, letPlan1.sym)(letPlan2.body) + letPlan1.body = SeqPlan(letPlan1.body, + transferNonPatmatGenedLets(plan2, newPlan2Body)) + Some(plan1) // that's the original plan1, on purpose - class ElimRedundant(seenTests: SeenTests) extends PlanTransform { - override def apply(plan: TestPlan): Plan = { - val normPlan = normalize(plan) - seenTests.get(normPlan) match { - case Some(outcome) => - apply(if (outcome) plan.onSuccess else plan.onFailure) - case None => - plan.onSuccess = new ElimRedundant(seenTests + (normPlan -> true))(plan.onSuccess) - plan.onFailure = new ElimRedundant(seenTests + (normPlan -> false))(plan.onFailure) - plan - } - } - override def apply(plan: LabelledPlan): Plan = { - plan.body = apply(plan.body) - for (seenTests1 <- seenAtLabel.get(plan.sym)) - labelled(plan.sym) = new ElimRedundant(seenTests1)(labelled(plan.sym)) - plan - } - override def apply(plan: CallPlan): Plan = { - val label = plan.label - def redirect(target: Plan): Plan = { - def forward(tst: TestPlan) = seenTests.get(tst) match { - case Some(true) => redirect(tst.onSuccess) - case Some(false) => redirect(tst.onFailure) - case none => target - } - target match { - case tst: TestPlan => forward(tst) - case UnappTestPlan(tst) => forward(tst) - case _ => target + case _ => + None } } - redirect(labelled(label)) match { - case target: CallPlan => - apply(target) - case _ => - seenAtLabel(label) = seenAtLabel.get(label) match { - case Some(seenTests1) => intersect(seenTests1, seenTests) - case none => seenTests - } - plan - } - } - } - new ElimRedundant(Map())(plan) - } - - /** Inline labelled blocks that are referenced only once. - * Drop all labels that are not referenced anymore after this. - */ - private def inlineLabelled(plan: Plan) = { - val refCount = labelRefCount(plan) - def toDrop(sym: Symbol) = labelled.contains(sym) && refCount(sym) <= 1 - class Inliner extends PlanTransform { - override def apply(plan: LabelledPlan): Plan = - if (toDrop(plan.sym)) apply(plan.body) else super.apply(plan) - override def apply(plan: CallPlan): Plan = { - if (refCount(plan.label) == 1) apply(labelled(plan.label)) - else plan - } - } - (new Inliner)(plan) - } - - /** Merge variables that have the same right hand side. - * Propagate common variable bindings as parameters into case labels. - */ - private def mergeVars(plan: Plan): Plan = { - class RHS(val tree: Tree) { - override def equals(that: Any) = that match { - case that: RHS => this.tree === that.tree - case _ => false - } - override def hashCode: Int = tree.hash - } - type SeenVars = Map[RHS, TermSymbol] - - /** The variables known at entry to label */ - val seenAtLabel = newMutableSymbolMap[SeenVars] - /** Parameters of label; these are passed additional variables - * which are known at all callsites. - */ - val paramsOfLabel = newMutableSymbolMap[SeenVars] - - class Merge(seenVars: SeenVars) extends PlanTransform { - override val treeMap = new TreeMap { - override def transform(tree: Tree)(implicit ctx: Context) = tree match { - case tree: Ident => - val sym = tree.symbol - initializer.get(sym) match { - case Some(id: Ident @unchecked) - if isPatmatGenerated(sym) && isPatmatGenerated(id.symbol) => - transform(id) - case none => tree + plan.head = apply(plan.head) + plan.tail = apply(plan.tail) + plan.tail match { + case SeqPlan(tailHead, tailTail) => + tryMerge(plan.head, tailHead) match { + case Some(merged) => SeqPlan(apply(merged), tailTail) + case none => plan } - case _ => - super.transform(tree) - } - } - - override def apply(plan: LetPlan): Plan = { - initializer(plan.sym) = apply(initializer(plan.sym)) - val seenVars1 = - if (isPatmatGenerated(plan.sym)) { - val thisRhs = new RHS(initializer(plan.sym)) - seenVars.get(thisRhs) match { - case Some(seen) => - initializer(plan.sym) = ref(seen) - seenVars - case none => - seenVars.updated(thisRhs, plan.sym) + case tail => + tryMerge(plan.head, tail) match { + case Some(merged) => apply(merged) + case none => plan } - } - else seenVars - plan.body = new Merge(seenVars1)(plan.body) - plan - } - - override def apply(plan: LabelledPlan): Plan = { - seenAtLabel(plan.sym) = seenVars - plan.body = apply(plan.body) - val paramsMap = paramsOfLabel.getOrElse(plan.sym, Map()) - plan.params = paramsMap.values.toList.sortBy(_.name.toString) - val seenVars1 = seenVars ++ paramsMap - labelled(plan.sym) = new Merge(seenVars1)(labelled(plan.sym)) - plan - } - - override def apply(plan: CallPlan): Plan = { - paramsOfLabel(plan.label) = paramsOfLabel.get(plan.label) match { - case Some(params) => - params.filter { case (rhs, _) => seenVars.contains(rhs) } - case none => - for ((rhs, _) <- seenVars if !seenAtLabel(plan.label).contains(rhs)) - yield (rhs, newVar(rhs.tree, Param)) } - val newArgs = - for { - (rhs, actual) <- seenVars.toList - formal <- paramsOfLabel(plan.label).get(rhs) - } - yield (formal -> actual) - if (plan.args.isEmpty) { plan.args = newArgs; plan } - else if (newArgs == plan.args) plan - else CallPlan(plan.label, newArgs) } } - (new Merge(Map()))(plan) + new MergeTests()(plan) } /** Inline let-bound trees that are referenced only once. @@ -745,16 +597,6 @@ object PatternMatcher { plan } } - override def apply(plan: LabelledPlan): Plan = { - plan.params = plan.params.filter(refCount(_) != 0) - super.apply(plan) - } - override def apply(plan: CallPlan): Plan = { - plan.args = plan.args - .filter(formalActual => refCount(formalActual._1) != 0) - .sortBy(_._1.name.toString) - plan - } } Inliner(plan) } @@ -817,42 +659,93 @@ object PatternMatcher { } } + @tailrec + private def canFallThrough(plan: Plan): Boolean = plan match { + case _:ReturnPlan | _:ResultPlan => false + case _:TestPlan | _:LabeledPlan => true + case LetPlan(_, body) => canFallThrough(body) + case SeqPlan(_, tail) => canFallThrough(tail) + } + /** Collect longest list of plans that represent possible cases of * a switch, including a last default case, by starting with this * plan and following onSuccess plans. */ - private def collectSwitchCases(plan: TestPlan): List[Plan] = { + private def collectSwitchCases(scrutinee: Tree, plan: SeqPlan): List[(List[Tree], Plan)] = { def isSwitchableType(tpe: Type): Boolean = (tpe isRef defn.IntClass) || (tpe isRef defn.ByteClass) || (tpe isRef defn.ShortClass) || (tpe isRef defn.CharClass) - val scrutinee = plan.scrutinee - def isIntConst(tree: Tree) = tree match { case Literal(const) => const.isIntRange case _ => false } - def recur(plan: Plan): List[Plan] = plan match { - case TestPlan(EqualTest(tree), scrut, _, _, onf) - if scrut === scrutinee && isIntConst(tree) => - plan :: recur(onf) + // An extractor to recover the shape of plans that can become alternatives + object AlternativesPlan { + def unapply(plan: LabeledPlan): Option[(List[Tree], Plan)] = { + plan.expr match { + case SeqPlan(LabeledPlan(innerLabel, innerPlan), ons) if !canFallThrough(ons) => + val outerLabel = plan.sym + val alts = List.newBuilder[Tree] + def rec(innerPlan: Plan): Boolean = innerPlan match { + case SeqPlan(TestPlan(EqualTest(tree), scrut, _, ReturnPlan(`innerLabel`)), tail) + if scrut === scrutinee && isIntConst(tree) => + alts += tree + rec(tail) + case ReturnPlan(`outerLabel`) => + true + case _ => + false + } + if (rec(innerPlan)) + Some((alts.result(), ons)) + else + None + + case _ => + None + } + } + } + + def recur(plan: Plan): List[(List[Tree], Plan)] = plan match { + case SeqPlan(testPlan @ TestPlan(EqualTest(tree), scrut, _, ons), tail) + if scrut === scrutinee && isIntConst(tree) && !canFallThrough(ons) => + (tree :: Nil, ons) :: recur(tail) + case SeqPlan(AlternativesPlan(alts, ons), tail) => + (alts, ons) :: recur(tail) case _ => - plan :: Nil + (Nil, plan) :: Nil } if (isSwitchableType(scrutinee.tpe.widen)) recur(plan) else Nil } + private def hasEnoughSwitchCases(cases: List[(List[Tree], Plan)], required: Int): Boolean = { + // 1 because of the default case + required <= 1 || { + cases match { + case (alts, _) :: cases1 => hasEnoughSwitchCases(cases1, required - alts.size) + case _ => false + } + } + } + /** Emit cases of a switch */ - private def emitSwitchCases(cases: List[Plan]): List[CaseDef] = (cases: @unchecked) match { - case (default: Plan) :: Nil => - CaseDef(Underscore(defn.IntType), EmptyTree, emit(default)) :: Nil - case TestPlan(EqualTest(tree), _, _, ons, _) :: cases1 => - CaseDef(tree, EmptyTree, emit(ons)) :: emitSwitchCases(cases1) + private def emitSwitchCases(cases: List[(List[Tree], Plan)]): List[CaseDef] = (cases: @unchecked) match { + case (alts, ons) :: cases1 => + val pat = alts match { + case alt :: Nil => alt + case Nil => Underscore(defn.IntType) // default case + case _ => Alternative(alts) + } + CaseDef(pat, EmptyTree, emit(ons)) :: emitSwitchCases(cases1) + case nil => + Nil } /** If selfCheck is `true`, used to check whether a tree gets generated twice */ @@ -861,63 +754,72 @@ object PatternMatcher { /** Translate plan to tree */ private def emit(plan: Plan): Tree = { if (selfCheck) { - assert(plan.isInstanceOf[CallPlan] || !emitted.contains(plan.id), plan.id) + assert(plan.isInstanceOf[ReturnPlan] || !emitted.contains(plan.id), plan.id) emitted += plan.id } plan match { case plan: TestPlan => - val switchCases = collectSwitchCases(plan) - if (switchCases.lengthCompare(MinSwitchCases) >= 0) // at least 3 cases + default - Match(plan.scrutinee, emitSwitchCases(switchCases)) - else { - /** Merge nested `if`s that have the same `else` branch into a single `if`. - * This optimization targets calls to label defs for case failure jumps to next case. - * - * Plan for - * ``` - * val x1: Int = ... - * val x2: Int = ... - * if (x1 == y1) { - * if (x2 == y2) someCode - * else label$1() - * } else label$1() - * ``` - * is emitted as - * ``` - * val x1: Int = ... - * val x2: Int = ... - * if (x1 == y1 && x2 == y2) someCode - * else label$1() - * ``` - */ - def emitWithMashedConditions(plans: List[TestPlan]): Tree = { - val plan = plans.head - plan.onSuccess match { - case plan2: TestPlan if plan.onFailure == plan2.onFailure => - emitWithMashedConditions(plan2 :: plans) - case _ => - def emitCondWithPos(plan: TestPlan) = emitCondition(plan).withPos(plan.pos) - val conditions = - plans.foldRight[Tree](EmptyTree) { (otherPlan, acc) => - if (acc.isEmpty) emitCondWithPos(otherPlan) - else acc.select(nme.ZAND).appliedTo(emitCondWithPos(otherPlan)) - } - If(conditions, emit(plan.onSuccess), emit(plan.onFailure)) - } - + /** Merge nested `if`s that have the same `else` branch into a single `if`. + * This optimization targets calls to label defs for case failure jumps to next case. + * + * Plan for + * ``` + * val x1: Int = ... + * val x2: Int = ... + * if (x1 == y1) { + * if (x2 == y2) someCode + * else label$1() + * } else label$1() + * ``` + * is emitted as + * ``` + * val x1: Int = ... + * val x2: Int = ... + * if (x1 == y1 && x2 == y2) someCode + * else label$1() + * ``` + */ + def emitWithMashedConditions(plans: List[TestPlan]): Tree = { + val plan = plans.head + plan.onSuccess match { + case plan2: TestPlan => + emitWithMashedConditions(plan2 :: plans) + case _ => + def emitCondWithPos(plan: TestPlan) = emitCondition(plan).withPos(plan.pos) + val conditions = + plans.foldRight[Tree](EmptyTree) { (otherPlan, acc) => + if (acc.isEmpty) emitCondWithPos(otherPlan) + else acc.select(nme.ZAND).appliedTo(emitCondWithPos(otherPlan)) + } + If(conditions, emit(plan.onSuccess), unitLiteral) } - emitWithMashedConditions(plan :: Nil) } + emitWithMashedConditions(plan :: Nil) case LetPlan(sym, body) => seq(ValDef(sym, initializer(sym).ensureConforms(sym.info)) :: Nil, emit(body)) - case LabelledPlan(label, body, params) => - label.info = MethodType.fromSymbols(params, resultType) - val labelDef = DefDef(label, Nil, params :: Nil, resultType, emit(labelled(label))) - seq(labelDef :: Nil, emit(body)) - case CodePlan(tree) => - tree - case CallPlan(label, args) => - ref(label).appliedToArgs(args.map { case (_, actual) => ref(actual) }) + case LabeledPlan(label, expr) => + Labeled(label, emit(expr)) + case ReturnPlan(label) => + Return(Literal(Constant(())), ref(label)) + case plan: SeqPlan => + def default = seq(emit(plan.head) :: Nil, emit(plan.tail)) + def maybeEmitSwitch(scrutinee: Tree): Tree = { + val switchCases = collectSwitchCases(scrutinee, plan) + if (hasEnoughSwitchCases(switchCases, MinSwitchCases)) // at least 3 cases + default + Match(scrutinee, emitSwitchCases(switchCases)) + else + default + } + plan.head match { + case testPlan: TestPlan => + maybeEmitSwitch(testPlan.scrutinee) + case LabeledPlan(_, SeqPlan(LabeledPlan(_, SeqPlan(testPlan: TestPlan, _)), _)) => + maybeEmitSwitch(testPlan.scrutinee) + case _ => + default + } + case ResultPlan(tree) => + Return(tree, ref(resultLabel)) } } @@ -937,26 +839,25 @@ object PatternMatcher { seen += plan.id sb append s"\n${plan.id}: " plan match { - case TestPlan(test, scrutinee, _, ons, onf) => - sb.append(i"$scrutinee ? ${showTest(test)}(${ons.id}, ${onf.id})") + case TestPlan(test, scrutinee, _, ons) => + sb.append(i"$scrutinee ? ${showTest(test)}(${ons.id})") showPlan(ons) - showPlan(onf) case LetPlan(sym, body) => sb.append(i"Let($sym = ${initializer(sym)}}, ${body.id})") sb.append(s", refcount = ${vrefCount(sym)}") showPlan(body) - case LabelledPlan(label, body, params) => - val labeld = labelled(label) - def showParam(param: Symbol) = - i"$param: ${param.info}, refCount = ${vrefCount(param)}" - sb.append(i"Labelled($label(${params.map(showParam)}%, %) = ${labeld.id}, ${body.id})") + case LabeledPlan(label, expr) => + sb.append(i"Labeled($label: { ${expr.id} })") sb.append(s", refcount = ${lrefCount(label)}") - showPlan(body) - showPlan(labeld) - case CodePlan(tree) => + showPlan(expr) + case ReturnPlan(label) => + sb.append(s"Return($label)") + case SeqPlan(head, tail) => + sb.append(s"Seq(${head.id}, ${tail.id})") + showPlan(head) + showPlan(tail) + case ResultPlan(tree) => sb.append(tree.show) - case CallPlan(label, params) => - sb.append(s"Call($label(${params.map(_._2)}%, %)") } } showPlan(plan) @@ -992,10 +893,7 @@ object PatternMatcher { } val optimizations: List[(String, Plan => Plan)] = List( - "hoistLabels" -> hoistLabels, - "elimRedundantTests" -> elimRedundantTests, - "inlineLabelled" -> inlineLabelled, - "mergeVars" -> mergeVars, + "mergeTests" -> mergeTests, "inlineVars" -> inlineVars ) @@ -1010,7 +908,7 @@ object PatternMatcher { } val result = emit(plan) checkSwitch(tree, result) - result + Labeled(resultLabel, result) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 19ed183f357e..bbb93f422fb6 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -191,7 +191,7 @@ class TreeChecker extends Phase with SymTransformer { def assertDefined(tree: untpd.Tree)(implicit ctx: Context) = if ( tree.symbol.maybeOwner.isTerm && - !(tree.symbol.is(Label) && !tree.symbol.owner.isClass && ctx.phase.labelsReordered) // labeldefs breaks scoping + !(tree.symbol.is(Label | Method) && !tree.symbol.owner.isClass && ctx.phase.labelsReordered) // labeldefs breaks scoping ) assert(nowDefinedSyms contains tree.symbol, i"undefined symbol ${tree.symbol} at line " + tree.pos.line) @@ -446,6 +446,22 @@ class TreeChecker extends Phase with SymTransformer { super.typedStats(trees, exprOwner) } + override def typedLabeled(tree: untpd.Labeled)(implicit ctx: Context): Labeled = { + checkOwner(tree.bind) + withDefinedSyms(tree.bind :: Nil) { super.typedLabeled(tree) } + } + + override def typedReturn(tree: untpd.Return)(implicit ctx: Context): Return = { + val tree1 = super.typedReturn(tree) + val from = tree1.from + val fromSym = from.symbol + if (fromSym.is(Label)) { + assert(!fromSym.is(Method), i"return from a label-def $fromSym at $tree") + assertDefined(from) + } + tree1 + } + override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(implicit ctx: Context): Tree = tree diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index afc4e83439e1..1f2a7a4813d1 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -466,6 +466,9 @@ trait TypeAssigner { def assignType(tree: untpd.Match, cases: List[CaseDef])(implicit ctx: Context) = tree.withType(ctx.typeComparer.lub(cases.tpes)) + def assignType(tree: untpd.Labeled)(implicit ctx: Context) = + tree.withType(tree.bind.symbol.info) + def assignType(tree: untpd.Return)(implicit ctx: Context) = tree.withType(defn.NothingType) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ee076403eb35..9fdce86dfc41 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1069,6 +1069,12 @@ class Typer extends Namer caseRest(pat1)(gadtCtx.fresh.setNewScope) } + def typedLabeled(tree: untpd.Labeled)(implicit ctx: Context): Labeled = track("typedLabeled") { + val bind1 = typedBind(tree.bind, WildcardType).asInstanceOf[Bind] + val expr1 = typed(tree.expr, bind1.symbol.info) + assignType(cpy.Labeled(tree)(bind1, expr1)) + } + def typedReturn(tree: untpd.Return)(implicit ctx: Context): Return = track("typedReturn") { def returnProto(owner: Symbol, locals: Scope): Type = if (owner.isConstructor) defn.UnitType @@ -1809,6 +1815,7 @@ class Typer extends Namer typedClassDef(tree, sym.asClass)(ctx.localContext(tree, sym).setMode(ctx.mode &~ Mode.InSuperCall)) else typedTypeDef(tree, sym)(ctx.localContext(tree, sym).setNewScope) + case tree: untpd.Labeled => typedLabeled(tree) case _ => typedUnadapted(desugar(tree), pt, locked) } } diff --git a/scala-backend b/scala-backend index 3fe795094b92..011d5c333d52 160000 --- a/scala-backend +++ b/scala-backend @@ -1 +1 @@ -Subproject commit 3fe795094b92920ffcbf3f1e2cdbfabc874ec555 +Subproject commit 011d5c333d52fc6d9e45b9e33614abe9ac19a851 diff --git a/tests/pos-special/fatal-warnings/switches.scala b/tests/pos-special/fatal-warnings/switches.scala new file mode 100644 index 000000000000..aa312499d3d7 --- /dev/null +++ b/tests/pos-special/fatal-warnings/switches.scala @@ -0,0 +1,33 @@ +import scala.annotation.switch + +class Test { + import Test._ + + def test1(x: Int): Int = (x: @switch) match { + case 1 => 1 + case 2 | 3 | 4 => 2 + case 65 => 3 + case 72 => 4 + } + + def test2(c: Char): Boolean = (c: @switch) match { + case LF | CR | FF | SU => true + case _ => false + } + + // #1313 + def test3(x: Int, y: Int): Int = (x: @switch) match { + case 6 if y > 5 => 1 + case 6 => 2 + case 12 => 3 + case 14 => 4 + case _ => 5 + } +} + +object Test { + final val LF = '\u000A' + final val CR = '\u000D' + final val FF = '\u000C' + final val SU = '\u001A' +} diff --git a/tests/pos/t10387b.scala b/tests/pos/t10387b.scala new file mode 100644 index 000000000000..6a9dece62927 --- /dev/null +++ b/tests/pos/t10387b.scala @@ -0,0 +1,269 @@ +object foo { + abstract sealed class num + final case class One() extends num + final case class Bit0(a: num) extends num + final case class Bit1(a: num) extends num + + abstract sealed class char + final case class zero_char() extends char + final case class Char(a: num) extends char + + def integer_of_char(x0: char): BigInt = x0 match { + case Char(Bit0(Bit0(Bit0(Bit0(Bit0(Bit0(Bit0(One())))))))) => BigInt(128) + case Char(Bit0(Bit0(Bit0(Bit0(Bit0(Bit0(Bit1(One())))))))) => BigInt(192) + case Char(Bit0(Bit0(Bit0(Bit0(Bit0(Bit0(One()))))))) => BigInt(64) + case Char(Bit0(Bit0(Bit0(Bit0(Bit0(Bit1(Bit0(One())))))))) => BigInt(160) + case Char(Bit0(Bit0(Bit0(Bit0(Bit0(Bit1(Bit1(One())))))))) => BigInt(224) + case Char(Bit0(Bit0(Bit0(Bit0(Bit0(Bit1(One()))))))) => BigInt(96) + case Char(Bit0(Bit0(Bit0(Bit0(Bit0(One())))))) => BigInt(32) + case Char(Bit0(Bit0(Bit0(Bit0(Bit1(Bit0(Bit0(One())))))))) => BigInt(144) + case Char(Bit0(Bit0(Bit0(Bit0(Bit1(Bit0(Bit1(One())))))))) => BigInt(208) + case Char(Bit0(Bit0(Bit0(Bit0(Bit1(Bit0(One()))))))) => BigInt(80) + case Char(Bit0(Bit0(Bit0(Bit0(Bit1(Bit1(Bit0(One())))))))) => BigInt(176) + case Char(Bit0(Bit0(Bit0(Bit0(Bit1(Bit1(Bit1(One())))))))) => BigInt(240) + case Char(Bit0(Bit0(Bit0(Bit0(Bit1(Bit1(One()))))))) => BigInt(112) + case Char(Bit0(Bit0(Bit0(Bit0(Bit1(One())))))) => BigInt(48) + case Char(Bit0(Bit0(Bit0(Bit0(One()))))) => BigInt(16) + case Char(Bit0(Bit0(Bit0(Bit1(Bit0(Bit0(Bit0(One())))))))) => BigInt(136) + case Char(Bit0(Bit0(Bit0(Bit1(Bit0(Bit0(Bit1(One())))))))) => BigInt(200) + case Char(Bit0(Bit0(Bit0(Bit1(Bit0(Bit0(One()))))))) => BigInt(72) + case Char(Bit0(Bit0(Bit0(Bit1(Bit0(Bit1(Bit0(One())))))))) => BigInt(168) + case Char(Bit0(Bit0(Bit0(Bit1(Bit0(Bit1(Bit1(One())))))))) => BigInt(232) + case Char(Bit0(Bit0(Bit0(Bit1(Bit0(Bit1(One()))))))) => BigInt(104) + case Char(Bit0(Bit0(Bit0(Bit1(Bit0(One())))))) => BigInt(40) + case Char(Bit0(Bit0(Bit0(Bit1(Bit1(Bit0(Bit0(One())))))))) => BigInt(152) + case Char(Bit0(Bit0(Bit0(Bit1(Bit1(Bit0(Bit1(One())))))))) => BigInt(216) + case Char(Bit0(Bit0(Bit0(Bit1(Bit1(Bit0(One()))))))) => BigInt(88) + case Char(Bit0(Bit0(Bit0(Bit1(Bit1(Bit1(Bit0(One())))))))) => BigInt(184) + case Char(Bit0(Bit0(Bit0(Bit1(Bit1(Bit1(Bit1(One())))))))) => BigInt(248) + case Char(Bit0(Bit0(Bit0(Bit1(Bit1(Bit1(One()))))))) => BigInt(120) + case Char(Bit0(Bit0(Bit0(Bit1(Bit1(One())))))) => BigInt(56) + case Char(Bit0(Bit0(Bit0(Bit1(One()))))) => BigInt(24) + case Char(Bit0(Bit0(Bit0(One())))) => BigInt(8) + case Char(Bit0(Bit0(Bit1(Bit0(Bit0(Bit0(Bit0(One())))))))) => BigInt(132) + case Char(Bit0(Bit0(Bit1(Bit0(Bit0(Bit0(Bit1(One())))))))) => BigInt(196) + case Char(Bit0(Bit0(Bit1(Bit0(Bit0(Bit0(One()))))))) => BigInt(68) + case Char(Bit0(Bit0(Bit1(Bit0(Bit0(Bit1(Bit0(One())))))))) => BigInt(164) + case Char(Bit0(Bit0(Bit1(Bit0(Bit0(Bit1(Bit1(One())))))))) => BigInt(228) + case Char(Bit0(Bit0(Bit1(Bit0(Bit0(Bit1(One()))))))) => BigInt(100) + case Char(Bit0(Bit0(Bit1(Bit0(Bit0(One())))))) => BigInt(36) + case Char(Bit0(Bit0(Bit1(Bit0(Bit1(Bit0(Bit0(One())))))))) => BigInt(148) + case Char(Bit0(Bit0(Bit1(Bit0(Bit1(Bit0(Bit1(One())))))))) => BigInt(212) + case Char(Bit0(Bit0(Bit1(Bit0(Bit1(Bit0(One()))))))) => BigInt(84) + case Char(Bit0(Bit0(Bit1(Bit0(Bit1(Bit1(Bit0(One())))))))) => BigInt(180) + case Char(Bit0(Bit0(Bit1(Bit0(Bit1(Bit1(Bit1(One())))))))) => BigInt(244) + case Char(Bit0(Bit0(Bit1(Bit0(Bit1(Bit1(One()))))))) => BigInt(116) + case Char(Bit0(Bit0(Bit1(Bit0(Bit1(One())))))) => BigInt(52) + case Char(Bit0(Bit0(Bit1(Bit0(One()))))) => BigInt(20) + case Char(Bit0(Bit0(Bit1(Bit1(Bit0(Bit0(Bit0(One())))))))) => BigInt(140) + case Char(Bit0(Bit0(Bit1(Bit1(Bit0(Bit0(Bit1(One())))))))) => BigInt(204) + case Char(Bit0(Bit0(Bit1(Bit1(Bit0(Bit0(One()))))))) => BigInt(76) + case Char(Bit0(Bit0(Bit1(Bit1(Bit0(Bit1(Bit0(One())))))))) => BigInt(172) + case Char(Bit0(Bit0(Bit1(Bit1(Bit0(Bit1(Bit1(One())))))))) => BigInt(236) + case Char(Bit0(Bit0(Bit1(Bit1(Bit0(Bit1(One()))))))) => BigInt(108) + case Char(Bit0(Bit0(Bit1(Bit1(Bit0(One())))))) => BigInt(44) + case Char(Bit0(Bit0(Bit1(Bit1(Bit1(Bit0(Bit0(One())))))))) => BigInt(156) + case Char(Bit0(Bit0(Bit1(Bit1(Bit1(Bit0(Bit1(One())))))))) => BigInt(220) + case Char(Bit0(Bit0(Bit1(Bit1(Bit1(Bit0(One()))))))) => BigInt(92) + case Char(Bit0(Bit0(Bit1(Bit1(Bit1(Bit1(Bit0(One())))))))) => BigInt(188) + case Char(Bit0(Bit0(Bit1(Bit1(Bit1(Bit1(Bit1(One())))))))) => BigInt(252) + case Char(Bit0(Bit0(Bit1(Bit1(Bit1(Bit1(One()))))))) => BigInt(124) + case Char(Bit0(Bit0(Bit1(Bit1(Bit1(One())))))) => BigInt(60) + case Char(Bit0(Bit0(Bit1(Bit1(One()))))) => BigInt(28) + case Char(Bit0(Bit0(Bit1(One())))) => BigInt(12) + case Char(Bit0(Bit0(One()))) => BigInt(4) + case Char(Bit0(Bit1(Bit0(Bit0(Bit0(Bit0(Bit0(One())))))))) => BigInt(130) + case Char(Bit0(Bit1(Bit0(Bit0(Bit0(Bit0(Bit1(One())))))))) => BigInt(194) + case Char(Bit0(Bit1(Bit0(Bit0(Bit0(Bit0(One()))))))) => BigInt(66) + case Char(Bit0(Bit1(Bit0(Bit0(Bit0(Bit1(Bit0(One())))))))) => BigInt(162) + case Char(Bit0(Bit1(Bit0(Bit0(Bit0(Bit1(Bit1(One())))))))) => BigInt(226) + case Char(Bit0(Bit1(Bit0(Bit0(Bit0(Bit1(One()))))))) => BigInt(98) + case Char(Bit0(Bit1(Bit0(Bit0(Bit0(One())))))) => BigInt(34) + case Char(Bit0(Bit1(Bit0(Bit0(Bit1(Bit0(Bit0(One())))))))) => BigInt(146) + case Char(Bit0(Bit1(Bit0(Bit0(Bit1(Bit0(Bit1(One())))))))) => BigInt(210) + case Char(Bit0(Bit1(Bit0(Bit0(Bit1(Bit0(One()))))))) => BigInt(82) + case Char(Bit0(Bit1(Bit0(Bit0(Bit1(Bit1(Bit0(One())))))))) => BigInt(178) + case Char(Bit0(Bit1(Bit0(Bit0(Bit1(Bit1(Bit1(One())))))))) => BigInt(242) + case Char(Bit0(Bit1(Bit0(Bit0(Bit1(Bit1(One()))))))) => BigInt(114) + case Char(Bit0(Bit1(Bit0(Bit0(Bit1(One())))))) => BigInt(50) + case Char(Bit0(Bit1(Bit0(Bit0(One()))))) => BigInt(18) + case Char(Bit0(Bit1(Bit0(Bit1(Bit0(Bit0(Bit0(One())))))))) => BigInt(138) + case Char(Bit0(Bit1(Bit0(Bit1(Bit0(Bit0(Bit1(One())))))))) => BigInt(202) + case Char(Bit0(Bit1(Bit0(Bit1(Bit0(Bit0(One()))))))) => BigInt(74) + case Char(Bit0(Bit1(Bit0(Bit1(Bit0(Bit1(Bit0(One())))))))) => BigInt(170) + case Char(Bit0(Bit1(Bit0(Bit1(Bit0(Bit1(Bit1(One())))))))) => BigInt(234) + case Char(Bit0(Bit1(Bit0(Bit1(Bit0(Bit1(One()))))))) => BigInt(106) + case Char(Bit0(Bit1(Bit0(Bit1(Bit0(One())))))) => BigInt(42) + case Char(Bit0(Bit1(Bit0(Bit1(Bit1(Bit0(Bit0(One())))))))) => BigInt(154) + case Char(Bit0(Bit1(Bit0(Bit1(Bit1(Bit0(Bit1(One())))))))) => BigInt(218) + case Char(Bit0(Bit1(Bit0(Bit1(Bit1(Bit0(One()))))))) => BigInt(90) + case Char(Bit0(Bit1(Bit0(Bit1(Bit1(Bit1(Bit0(One())))))))) => BigInt(186) + case Char(Bit0(Bit1(Bit0(Bit1(Bit1(Bit1(Bit1(One())))))))) => BigInt(250) + case Char(Bit0(Bit1(Bit0(Bit1(Bit1(Bit1(One()))))))) => BigInt(122) + case Char(Bit0(Bit1(Bit0(Bit1(Bit1(One())))))) => BigInt(58) + case Char(Bit0(Bit1(Bit0(Bit1(One()))))) => BigInt(26) + case Char(Bit0(Bit1(Bit0(One())))) => BigInt(10) + case Char(Bit0(Bit1(Bit1(Bit0(Bit0(Bit0(Bit0(One())))))))) => BigInt(134) + case Char(Bit0(Bit1(Bit1(Bit0(Bit0(Bit0(Bit1(One())))))))) => BigInt(198) + case Char(Bit0(Bit1(Bit1(Bit0(Bit0(Bit0(One()))))))) => BigInt(70) + case Char(Bit0(Bit1(Bit1(Bit0(Bit0(Bit1(Bit0(One())))))))) => BigInt(166) + case Char(Bit0(Bit1(Bit1(Bit0(Bit0(Bit1(Bit1(One())))))))) => BigInt(230) + case Char(Bit0(Bit1(Bit1(Bit0(Bit0(Bit1(One()))))))) => BigInt(102) + case Char(Bit0(Bit1(Bit1(Bit0(Bit0(One())))))) => BigInt(38) + case Char(Bit0(Bit1(Bit1(Bit0(Bit1(Bit0(Bit0(One())))))))) => BigInt(150) + case Char(Bit0(Bit1(Bit1(Bit0(Bit1(Bit0(Bit1(One())))))))) => BigInt(214) + case Char(Bit0(Bit1(Bit1(Bit0(Bit1(Bit0(One()))))))) => BigInt(86) + case Char(Bit0(Bit1(Bit1(Bit0(Bit1(Bit1(Bit0(One())))))))) => BigInt(182) + case Char(Bit0(Bit1(Bit1(Bit0(Bit1(Bit1(Bit1(One())))))))) => BigInt(246) + case Char(Bit0(Bit1(Bit1(Bit0(Bit1(Bit1(One()))))))) => BigInt(118) + case Char(Bit0(Bit1(Bit1(Bit0(Bit1(One())))))) => BigInt(54) + case Char(Bit0(Bit1(Bit1(Bit0(One()))))) => BigInt(22) + case Char(Bit0(Bit1(Bit1(Bit1(Bit0(Bit0(Bit0(One())))))))) => BigInt(142) + case Char(Bit0(Bit1(Bit1(Bit1(Bit0(Bit0(Bit1(One())))))))) => BigInt(206) + case Char(Bit0(Bit1(Bit1(Bit1(Bit0(Bit0(One()))))))) => BigInt(78) + case Char(Bit0(Bit1(Bit1(Bit1(Bit0(Bit1(Bit0(One())))))))) => BigInt(174) + case Char(Bit0(Bit1(Bit1(Bit1(Bit0(Bit1(Bit1(One())))))))) => BigInt(238) + case Char(Bit0(Bit1(Bit1(Bit1(Bit0(Bit1(One()))))))) => BigInt(110) + case Char(Bit0(Bit1(Bit1(Bit1(Bit0(One())))))) => BigInt(46) + case Char(Bit0(Bit1(Bit1(Bit1(Bit1(Bit0(Bit0(One())))))))) => BigInt(158) + case Char(Bit0(Bit1(Bit1(Bit1(Bit1(Bit0(Bit1(One())))))))) => BigInt(222) + case Char(Bit0(Bit1(Bit1(Bit1(Bit1(Bit0(One()))))))) => BigInt(94) + case Char(Bit0(Bit1(Bit1(Bit1(Bit1(Bit1(Bit0(One())))))))) => BigInt(190) + case Char(Bit0(Bit1(Bit1(Bit1(Bit1(Bit1(Bit1(One())))))))) => BigInt(254) + case Char(Bit0(Bit1(Bit1(Bit1(Bit1(Bit1(One()))))))) => BigInt(126) + case Char(Bit0(Bit1(Bit1(Bit1(Bit1(One())))))) => BigInt(62) + case Char(Bit0(Bit1(Bit1(Bit1(One()))))) => BigInt(30) + case Char(Bit0(Bit1(Bit1(One())))) => BigInt(14) + case Char(Bit0(Bit1(One()))) => BigInt(6) + case Char(Bit0(One())) => BigInt(2) + case Char(Bit1(Bit0(Bit0(Bit0(Bit0(Bit0(Bit0(One())))))))) => BigInt(129) + case Char(Bit1(Bit0(Bit0(Bit0(Bit0(Bit0(Bit1(One())))))))) => BigInt(193) + case Char(Bit1(Bit0(Bit0(Bit0(Bit0(Bit0(One()))))))) => BigInt(65) + case Char(Bit1(Bit0(Bit0(Bit0(Bit0(Bit1(Bit0(One())))))))) => BigInt(161) + case Char(Bit1(Bit0(Bit0(Bit0(Bit0(Bit1(Bit1(One())))))))) => BigInt(225) + case Char(Bit1(Bit0(Bit0(Bit0(Bit0(Bit1(One()))))))) => BigInt(97) + case Char(Bit1(Bit0(Bit0(Bit0(Bit0(One())))))) => BigInt(33) + case Char(Bit1(Bit0(Bit0(Bit0(Bit1(Bit0(Bit0(One())))))))) => BigInt(145) + case Char(Bit1(Bit0(Bit0(Bit0(Bit1(Bit0(Bit1(One())))))))) => BigInt(209) + case Char(Bit1(Bit0(Bit0(Bit0(Bit1(Bit0(One()))))))) => BigInt(81) + case Char(Bit1(Bit0(Bit0(Bit0(Bit1(Bit1(Bit0(One())))))))) => BigInt(177) + case Char(Bit1(Bit0(Bit0(Bit0(Bit1(Bit1(Bit1(One())))))))) => BigInt(241) + case Char(Bit1(Bit0(Bit0(Bit0(Bit1(Bit1(One()))))))) => BigInt(113) + case Char(Bit1(Bit0(Bit0(Bit0(Bit1(One())))))) => BigInt(49) + case Char(Bit1(Bit0(Bit0(Bit0(One()))))) => BigInt(17) + case Char(Bit1(Bit0(Bit0(Bit1(Bit0(Bit0(Bit0(One())))))))) => BigInt(137) + case Char(Bit1(Bit0(Bit0(Bit1(Bit0(Bit0(Bit1(One())))))))) => BigInt(201) + case Char(Bit1(Bit0(Bit0(Bit1(Bit0(Bit0(One()))))))) => BigInt(73) + case Char(Bit1(Bit0(Bit0(Bit1(Bit0(Bit1(Bit0(One())))))))) => BigInt(169) + case Char(Bit1(Bit0(Bit0(Bit1(Bit0(Bit1(Bit1(One())))))))) => BigInt(233) + case Char(Bit1(Bit0(Bit0(Bit1(Bit0(Bit1(One()))))))) => BigInt(105) + case Char(Bit1(Bit0(Bit0(Bit1(Bit0(One())))))) => BigInt(41) + case Char(Bit1(Bit0(Bit0(Bit1(Bit1(Bit0(Bit0(One())))))))) => BigInt(153) + case Char(Bit1(Bit0(Bit0(Bit1(Bit1(Bit0(Bit1(One())))))))) => BigInt(217) + case Char(Bit1(Bit0(Bit0(Bit1(Bit1(Bit0(One()))))))) => BigInt(89) + case Char(Bit1(Bit0(Bit0(Bit1(Bit1(Bit1(Bit0(One())))))))) => BigInt(185) + case Char(Bit1(Bit0(Bit0(Bit1(Bit1(Bit1(Bit1(One())))))))) => BigInt(249) + case Char(Bit1(Bit0(Bit0(Bit1(Bit1(Bit1(One()))))))) => BigInt(121) + case Char(Bit1(Bit0(Bit0(Bit1(Bit1(One())))))) => BigInt(57) + case Char(Bit1(Bit0(Bit0(Bit1(One()))))) => BigInt(25) + case Char(Bit1(Bit0(Bit0(One())))) => BigInt(9) + case Char(Bit1(Bit0(Bit1(Bit0(Bit0(Bit0(Bit0(One())))))))) => BigInt(133) + case Char(Bit1(Bit0(Bit1(Bit0(Bit0(Bit0(Bit1(One())))))))) => BigInt(197) + case Char(Bit1(Bit0(Bit1(Bit0(Bit0(Bit0(One()))))))) => BigInt(69) + case Char(Bit1(Bit0(Bit1(Bit0(Bit0(Bit1(Bit0(One())))))))) => BigInt(165) + case Char(Bit1(Bit0(Bit1(Bit0(Bit0(Bit1(Bit1(One())))))))) => BigInt(229) + case Char(Bit1(Bit0(Bit1(Bit0(Bit0(Bit1(One()))))))) => BigInt(101) + case Char(Bit1(Bit0(Bit1(Bit0(Bit0(One())))))) => BigInt(37) + case Char(Bit1(Bit0(Bit1(Bit0(Bit1(Bit0(Bit0(One())))))))) => BigInt(149) + case Char(Bit1(Bit0(Bit1(Bit0(Bit1(Bit0(Bit1(One())))))))) => BigInt(213) + case Char(Bit1(Bit0(Bit1(Bit0(Bit1(Bit0(One()))))))) => BigInt(85) + case Char(Bit1(Bit0(Bit1(Bit0(Bit1(Bit1(Bit0(One())))))))) => BigInt(181) + case Char(Bit1(Bit0(Bit1(Bit0(Bit1(Bit1(Bit1(One())))))))) => BigInt(245) + case Char(Bit1(Bit0(Bit1(Bit0(Bit1(Bit1(One()))))))) => BigInt(117) + case Char(Bit1(Bit0(Bit1(Bit0(Bit1(One())))))) => BigInt(53) + case Char(Bit1(Bit0(Bit1(Bit0(One()))))) => BigInt(21) + case Char(Bit1(Bit0(Bit1(Bit1(Bit0(Bit0(Bit0(One())))))))) => BigInt(141) + case Char(Bit1(Bit0(Bit1(Bit1(Bit0(Bit0(Bit1(One())))))))) => BigInt(205) + case Char(Bit1(Bit0(Bit1(Bit1(Bit0(Bit0(One()))))))) => BigInt(77) + case Char(Bit1(Bit0(Bit1(Bit1(Bit0(Bit1(Bit0(One())))))))) => BigInt(173) + case Char(Bit1(Bit0(Bit1(Bit1(Bit0(Bit1(Bit1(One())))))))) => BigInt(237) + case Char(Bit1(Bit0(Bit1(Bit1(Bit0(Bit1(One()))))))) => BigInt(109) + case Char(Bit1(Bit0(Bit1(Bit1(Bit0(One())))))) => BigInt(45) + case Char(Bit1(Bit0(Bit1(Bit1(Bit1(Bit0(Bit0(One())))))))) => BigInt(157) + case Char(Bit1(Bit0(Bit1(Bit1(Bit1(Bit0(Bit1(One())))))))) => BigInt(221) + case Char(Bit1(Bit0(Bit1(Bit1(Bit1(Bit0(One()))))))) => BigInt(93) + case Char(Bit1(Bit0(Bit1(Bit1(Bit1(Bit1(Bit0(One())))))))) => BigInt(189) + case Char(Bit1(Bit0(Bit1(Bit1(Bit1(Bit1(Bit1(One())))))))) => BigInt(253) + case Char(Bit1(Bit0(Bit1(Bit1(Bit1(Bit1(One()))))))) => BigInt(125) + case Char(Bit1(Bit0(Bit1(Bit1(Bit1(One())))))) => BigInt(61) + case Char(Bit1(Bit0(Bit1(Bit1(One()))))) => BigInt(29) + case Char(Bit1(Bit0(Bit1(One())))) => BigInt(13) + case Char(Bit1(Bit0(One()))) => BigInt(5) + case Char(Bit1(Bit1(Bit0(Bit0(Bit0(Bit0(Bit0(One())))))))) => BigInt(131) + case Char(Bit1(Bit1(Bit0(Bit0(Bit0(Bit0(Bit1(One())))))))) => BigInt(195) + case Char(Bit1(Bit1(Bit0(Bit0(Bit0(Bit0(One()))))))) => BigInt(67) + case Char(Bit1(Bit1(Bit0(Bit0(Bit0(Bit1(Bit0(One())))))))) => BigInt(163) + case Char(Bit1(Bit1(Bit0(Bit0(Bit0(Bit1(Bit1(One())))))))) => BigInt(227) + case Char(Bit1(Bit1(Bit0(Bit0(Bit0(Bit1(One()))))))) => BigInt(99) + case Char(Bit1(Bit1(Bit0(Bit0(Bit0(One())))))) => BigInt(35) + case Char(Bit1(Bit1(Bit0(Bit0(Bit1(Bit0(Bit0(One())))))))) => BigInt(147) + case Char(Bit1(Bit1(Bit0(Bit0(Bit1(Bit0(Bit1(One())))))))) => BigInt(211) + case Char(Bit1(Bit1(Bit0(Bit0(Bit1(Bit0(One()))))))) => BigInt(83) + case Char(Bit1(Bit1(Bit0(Bit0(Bit1(Bit1(Bit0(One())))))))) => BigInt(179) + case Char(Bit1(Bit1(Bit0(Bit0(Bit1(Bit1(Bit1(One())))))))) => BigInt(243) + case Char(Bit1(Bit1(Bit0(Bit0(Bit1(Bit1(One()))))))) => BigInt(115) + case Char(Bit1(Bit1(Bit0(Bit0(Bit1(One())))))) => BigInt(51) + case Char(Bit1(Bit1(Bit0(Bit0(One()))))) => BigInt(19) + case Char(Bit1(Bit1(Bit0(Bit1(Bit0(Bit0(Bit0(One())))))))) => BigInt(139) + case Char(Bit1(Bit1(Bit0(Bit1(Bit0(Bit0(Bit1(One())))))))) => BigInt(203) + case Char(Bit1(Bit1(Bit0(Bit1(Bit0(Bit0(One()))))))) => BigInt(75) + case Char(Bit1(Bit1(Bit0(Bit1(Bit0(Bit1(Bit0(One())))))))) => BigInt(171) + case Char(Bit1(Bit1(Bit0(Bit1(Bit0(Bit1(Bit1(One())))))))) => BigInt(235) + case Char(Bit1(Bit1(Bit0(Bit1(Bit0(Bit1(One()))))))) => BigInt(107) + case Char(Bit1(Bit1(Bit0(Bit1(Bit0(One())))))) => BigInt(43) + case Char(Bit1(Bit1(Bit0(Bit1(Bit1(Bit0(Bit0(One())))))))) => BigInt(155) + case Char(Bit1(Bit1(Bit0(Bit1(Bit1(Bit0(Bit1(One())))))))) => BigInt(219) + case Char(Bit1(Bit1(Bit0(Bit1(Bit1(Bit0(One()))))))) => BigInt(91) + case Char(Bit1(Bit1(Bit0(Bit1(Bit1(Bit1(Bit0(One())))))))) => BigInt(187) + case Char(Bit1(Bit1(Bit0(Bit1(Bit1(Bit1(Bit1(One())))))))) => BigInt(251) + case Char(Bit1(Bit1(Bit0(Bit1(Bit1(Bit1(One()))))))) => BigInt(123) + case Char(Bit1(Bit1(Bit0(Bit1(Bit1(One())))))) => BigInt(59) + case Char(Bit1(Bit1(Bit0(Bit1(One()))))) => BigInt(27) + case Char(Bit1(Bit1(Bit0(One())))) => BigInt(11) + case Char(Bit1(Bit1(Bit1(Bit0(Bit0(Bit0(Bit0(One())))))))) => BigInt(135) + case Char(Bit1(Bit1(Bit1(Bit0(Bit0(Bit0(Bit1(One())))))))) => BigInt(199) + case Char(Bit1(Bit1(Bit1(Bit0(Bit0(Bit0(One()))))))) => BigInt(71) + case Char(Bit1(Bit1(Bit1(Bit0(Bit0(Bit1(Bit0(One())))))))) => BigInt(167) + case Char(Bit1(Bit1(Bit1(Bit0(Bit0(Bit1(Bit1(One())))))))) => BigInt(231) + case Char(Bit1(Bit1(Bit1(Bit0(Bit0(Bit1(One()))))))) => BigInt(103) + case Char(Bit1(Bit1(Bit1(Bit0(Bit0(One())))))) => BigInt(39) + case Char(Bit1(Bit1(Bit1(Bit0(Bit1(Bit0(Bit0(One())))))))) => BigInt(151) + case Char(Bit1(Bit1(Bit1(Bit0(Bit1(Bit0(Bit1(One())))))))) => BigInt(215) + case Char(Bit1(Bit1(Bit1(Bit0(Bit1(Bit0(One()))))))) => BigInt(87) + case Char(Bit1(Bit1(Bit1(Bit0(Bit1(Bit1(Bit0(One())))))))) => BigInt(183) + case Char(Bit1(Bit1(Bit1(Bit0(Bit1(Bit1(Bit1(One())))))))) => BigInt(247) + case Char(Bit1(Bit1(Bit1(Bit0(Bit1(Bit1(One()))))))) => BigInt(119) + case Char(Bit1(Bit1(Bit1(Bit0(Bit1(One())))))) => BigInt(55) + case Char(Bit1(Bit1(Bit1(Bit0(One()))))) => BigInt(23) + case Char(Bit1(Bit1(Bit1(Bit1(Bit0(Bit0(Bit0(One())))))))) => BigInt(143) + case Char(Bit1(Bit1(Bit1(Bit1(Bit0(Bit0(Bit1(One())))))))) => BigInt(207) + case Char(Bit1(Bit1(Bit1(Bit1(Bit0(Bit0(One()))))))) => BigInt(79) + case Char(Bit1(Bit1(Bit1(Bit1(Bit0(Bit1(Bit0(One())))))))) => BigInt(175) + case Char(Bit1(Bit1(Bit1(Bit1(Bit0(Bit1(Bit1(One())))))))) => BigInt(239) + case Char(Bit1(Bit1(Bit1(Bit1(Bit0(Bit1(One()))))))) => BigInt(111) + case Char(Bit1(Bit1(Bit1(Bit1(Bit0(One())))))) => BigInt(47) + case Char(Bit1(Bit1(Bit1(Bit1(Bit1(Bit0(Bit0(One())))))))) => BigInt(159) + case Char(Bit1(Bit1(Bit1(Bit1(Bit1(Bit0(Bit1(One())))))))) => BigInt(223) + case Char(Bit1(Bit1(Bit1(Bit1(Bit1(Bit0(One()))))))) => BigInt(95) + case Char(Bit1(Bit1(Bit1(Bit1(Bit1(Bit1(Bit0(One())))))))) => BigInt(191) + case Char(Bit1(Bit1(Bit1(Bit1(Bit1(Bit1(Bit1(One())))))))) => BigInt(255) + case Char(Bit1(Bit1(Bit1(Bit1(Bit1(Bit1(One()))))))) => BigInt(127) + case Char(Bit1(Bit1(Bit1(Bit1(Bit1(One())))))) => BigInt(63) + case Char(Bit1(Bit1(Bit1(Bit1(One()))))) => BigInt(31) + case Char(Bit1(Bit1(Bit1(One())))) => BigInt(15) + case Char(Bit1(Bit1(One()))) => BigInt(7) + case Char(Bit1(One())) => BigInt(3) + case Char(One()) => BigInt(1) + case zero_char() => BigInt(0) + } +} diff --git a/tests/run/i4563.check b/tests/run/i4563.check new file mode 100644 index 000000000000..e1e87509de0d --- /dev/null +++ b/tests/run/i4563.check @@ -0,0 +1,3 @@ +Foo(10) +Foo(42) +Foo(10) diff --git a/tests/run/i4563.scala b/tests/run/i4563.scala new file mode 100644 index 000000000000..d4b36408833b --- /dev/null +++ b/tests/run/i4563.scala @@ -0,0 +1,19 @@ +case class Foo(str: String) extends AnyVal + +object Test { + def main(args: Array[String]): Unit = { + test(Foo("one")) + test(Foo("")) + test("two") + } + + def test(x: Any): Unit = { + val vc: Any = x match { + case Foo("") => + Foo("42") + case _ => + Foo("10") + } + println(vc) + } +} diff --git a/tests/run/reducable.scala b/tests/run/reducable.scala index d3c859d84979..a84f28844084 100644 --- a/tests/run/reducable.scala +++ b/tests/run/reducable.scala @@ -1,6 +1,4 @@ object Test extends App { - val xs = List(1, 2, 3) - object Cons { var count = 0 @@ -13,13 +11,52 @@ object Test extends App { } } - val res = xs match { - case Cons(0, Nil) => 1 - case Cons(_, Nil) => 2 - case Cons(0, _) => 3 - case Cons(1, ys) => 4 + object Guard { + var count = 0 + + def apply(): Boolean = { + count += 1 + false + } + } + + def reset(): Unit = { + Cons.count = 0 + Guard.count = 0 } - assert(res == 4, res) - assert(Cons.count ==1, Cons.count) + val xs = List(1, 2, 3) + test1(xs) + reset() + test2(xs) + + def test1(xs: List[Int]): Unit = { + val res = xs match { + case Cons(0, Nil) => 1 + case Cons(_, Nil) => 2 + case Cons(0, _) => 3 + case Cons(1, ys) => 4 + } + + assert(res == 4, res) + assert(Cons.count == 1, Cons.count) + } + + // #1313 + def test2(xs: List[Int]): Unit = { + val res = xs match { + case Cons(0, Nil) if Guard() => 1 + case Cons(0, Nil) => 2 + case Cons(_, Nil) if Guard() => 3 + case Cons(_, Nil) => 4 + case Cons(0, _) if Guard() => 5 + case Cons(0, _) => 6 + case Cons(1, ys) if Guard() => 7 + case Cons(1, ys) => 8 + } + + assert(res == 8, res) + assert(Cons.count == 1, Cons.count) + assert(Guard.count == 1, Guard.count) + } }