diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 8a7cd091ccb0..0c888b849857 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -32,6 +32,7 @@ object Printers { val plugins: Printer = noPrinter val simplify: Printer = noPrinter val subtyping: Printer = noPrinter + val tailrec: Printer = noPrinter val transforms: Printer = noPrinter val typr: Printer = noPrinter val unapp: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 40d0da605dbe..a9811d1da065 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1801,14 +1801,16 @@ object messages { case class TailrecNotApplicable(symbol: Symbol)(implicit ctx: Context) extends Message(TailrecNotApplicableID) { val kind: String = "Syntax" - val symbolKind: String = symbol.showKind - val msg: String = - if (symbol.is(Method)) - hl"TailRec optimisation not applicable, $symbol is neither ${"private"} nor ${"final"}." - else - hl"TailRec optimisation not applicable, ${symbolKind} isn't a method." - val explanation: String = - hl"A method annotated ${"@tailrec"} must be declared ${"private"} or ${"final"} so it can't be overridden." + val msg: String = { + val reason = + if (!symbol.is(Method)) hl"$symbol isn't a method" + else if (symbol.is(Deferred)) hl"$symbol is abstract" + else if (!symbol.isEffectivelyFinal) hl"$symbol is neither ${"private"} nor ${"final"} so can be overridden" + else hl"$symbol contains no recursive calls" + + s"TailRec optimisation not applicable, $reason" + } + val explanation: String = "" } case class FailureToEliminateExistential(tp: Type, tp1: Type, tp2: Type, boundSyms: List[Symbol])(implicit ctx: Context) diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 37411bb06bae..b6b51d54d95b 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -1,18 +1,19 @@ package dotty.tools.dotc package transform -import ast.Trees._ -import ast.{TreeTypeMap, tpd} -import core._ -import Constants.Constant -import Contexts.Context -import Decorators._ -import Symbols._ -import StdNames.nme -import Types._ -import NameKinds.{TailLabelName, TailLocalName, TailTempName} -import MegaPhase.MiniPhase -import reporting.diagnostic.messages.TailrecNotApplicable +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{TreeTypeMap, tpd} +import dotty.tools.dotc.config.Printers.tailrec +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.NameKinds.{TailLabelName, TailLocalName, TailTempName} +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.reporting.diagnostic.messages.TailrecNotApplicable +import dotty.tools.dotc.transform.MegaPhase.MiniPhase + +import scala.collection.mutable /** A Tail Rec Transformer. * @@ -29,7 +30,8 @@ import reporting.diagnostic.messages.TailrecNotApplicable * * When a method contains at least one tail-recursive call, its rhs * is wrapped in the following structure: - * {{{ + * + * ``` * var localForParam1: T1 = param1 * ... * while () { @@ -39,7 +41,7 @@ import reporting.diagnostic.messages.TailrecNotApplicable * } * } * } - * }}} + * ``` * * Self-recursive calls in tail-position are then replaced by (a) * reassigning the local `var`s substituting formal parameters and @@ -50,13 +52,16 @@ import reporting.diagnostic.messages.TailrecNotApplicable * * As a complete example of the transformation, the classical `fact` * function, defined as: - * {{{ + * + * ``` * def fact(n: Int, acc: Int): Int = * if (n == 0) acc * else fact(n - 1, acc * n) - * }}} + * ``` + * * is rewritten as: - * {{{ + * + * ``` * def fact(n: Int, acc: Int): Int = { * var acc$tailLocal1: Int = acc * var n$tailLocal1: Int = n @@ -76,7 +81,7 @@ import reporting.diagnostic.messages.TailrecNotApplicable * } * } * } - * }}} + * ``` * * As the JVM provides no way to jump from a method to another one, * non-recursive calls in tail-position are not optimized. @@ -102,103 +107,99 @@ import reporting.diagnostic.messages.TailrecNotApplicable * moved after erasure and adapted to emit `Labeled` blocks by Sébastien Doeraene */ class TailRec extends MiniPhase { - import TailRec._ - - import dotty.tools.dotc.ast.tpd._ + import tpd._ override def phaseName: String = TailRec.name override def runsAfter: Set[String] = Set(Erasure.name) // tailrec assumes erased types - override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context): tpd.Tree = { - val sym = tree.symbol - tree match { - case dd@DefDef(name, Nil, vparams :: Nil, tpt, _) - if (sym.isEffectivelyFinal) && !((sym is Flags.Accessor) || (dd.rhs eq EmptyTree)) => - val mandatory = sym.hasAnnotation(defn.TailrecAnnot) - cpy.DefDef(dd)(rhs = { - val defIsTopLevel = sym.owner.isClass - val origMeth = sym - val owner = ctx.owner.enclosingClass.asClass - - // Note: this can be split in two separate transforms(in different groups), - // than first one will collect info about which transformations and rewritings should be applied - // and second one will actually apply, - // now this speculatively transforms tree and throws away result in many cases - val transformer = new TailRecElimination(origMeth, owner, vparams.map(_.symbol), mandatory) - val rhsSemiTransformed = transformer.transform(dd.rhs) - - if (transformer.rewrote) { - val varForRewrittenThis = transformer.varForRewrittenThis - val rewrittenParamSyms = transformer.rewrittenParamSyms - val varsForRewrittenParamSyms = transformer.varsForRewrittenParamSyms - - val initialVarDefs = { - val initialParamVarDefs = (rewrittenParamSyms, varsForRewrittenParamSyms).zipped.map { - (param, local) => ValDef(local.asTerm, ref(param)) - } - varForRewrittenThis match { - case Some(local) => ValDef(local.asTerm, This(tree.symbol.owner.asClass)) :: initialParamVarDefs - case none => initialParamVarDefs + override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = { + val method = tree.symbol + val mandatory = method.hasAnnotation(defn.TailrecAnnot) + def noTailTransform(failureReported: Boolean) = { + // FIXME: want to report this error on `tree.namePos`, but + // because of extension method getting a weird pos, it is + // better to report on method symbol so there's no overlap. + // We don't report a new error if failures were reported + // during the transformation. + if (mandatory && !failureReported) + ctx.error(TailrecNotApplicable(method), method.pos) + + tree + } + + val isCandidate = method.isEffectivelyFinal && + !(method.is(Accessor) || tree.rhs.eq(EmptyTree)) + + if (isCandidate) { + val enclosingClass = method.enclosingClass.asClass + + // Note: this can be split in two separate transforms(in different groups), + // than first one will collect info about which transformations and rewritings should be applied + // and second one will actually apply, + // now this speculatively transforms tree and throws away result in many cases + val transformer = new TailRecElimination(method, enclosingClass, tree.vparamss.head.map(_.symbol), mandatory) + val rhsSemiTransformed = transformer.transform(tree.rhs) + + if (transformer.rewrote) { + val varForRewrittenThis = transformer.varForRewrittenThis + val rewrittenParamSyms = transformer.rewrittenParamSyms + val varsForRewrittenParamSyms = transformer.varsForRewrittenParamSyms + + val initialVarDefs = { + val initialParamVarDefs = (rewrittenParamSyms, varsForRewrittenParamSyms).zipped.map { + (param, local) => ValDef(local.asTerm, ref(param)) + } + varForRewrittenThis match { + case Some(local) => ValDef(local.asTerm, This(enclosingClass)) :: initialParamVarDefs + case none => initialParamVarDefs + } + } + + val rhsFullyTransformed = varForRewrittenThis match { + case Some(localThisSym) => + val thisRef = localThisSym.termRef + new TreeTypeMap( + typeMap = _.substThisUnlessStatic(enclosingClass, thisRef) + .subst(rewrittenParamSyms, varsForRewrittenParamSyms.map(_.termRef)), + treeMap = { + case tree: This if tree.symbol == enclosingClass => Ident(thisRef) + case tree => tree } - } + ).transform(rhsSemiTransformed) - val rhsFullyTransformed = varForRewrittenThis match { - case Some(localThisSym) => - val thisRef = localThisSym.termRef - new TreeTypeMap( - typeMap = _.substThisUnlessStatic(owner, thisRef) - .subst(rewrittenParamSyms, varsForRewrittenParamSyms.map(_.termRef)), - treeMap = { - case tree: This if tree.symbol == owner => Ident(thisRef) - case tree => tree - } - ).transform(rhsSemiTransformed) - - case none => - new TreeTypeMap( - typeMap = _.subst(rewrittenParamSyms, varsForRewrittenParamSyms.map(_.termRef)) - ).transform(rhsSemiTransformed) - } + case none => + new TreeTypeMap( + typeMap = _.subst(rewrittenParamSyms, varsForRewrittenParamSyms.map(_.termRef)) + ).transform(rhsSemiTransformed) + } - Block( - initialVarDefs, - WhileDo(EmptyTree, { - Labeled(transformer.continueLabel.asTerm, { - Return(rhsFullyTransformed, origMeth) - }) + cpy.DefDef(tree)(rhs = + Block( + initialVarDefs, + WhileDo(EmptyTree, { + Labeled(transformer.continueLabel.asTerm, { + Return(rhsFullyTransformed, method) }) - ) - } else { - if (mandatory) ctx.error( - "TailRec optimisation not applicable, method not tail recursive", - // FIXME: want to report this error on `dd.namePos`, but - // because of extension method getting a weird pos, it is - // better to report on symbol so there's no overlap - sym.pos - ) - dd.rhs - } - }) - case d: DefDef if d.symbol.hasAnnotation(defn.TailrecAnnot) => - ctx.error(TailrecNotApplicable(sym), sym.pos) - d - case _ => tree + }) + ) + ) + } + else noTailTransform(failureReported = transformer.failureReported) } - + else noTailTransform(failureReported = false) } - class TailRecElimination(method: Symbol, enclosingClass: Symbol, paramSyms: List[Symbol], isMandatory: Boolean) extends tpd.TreeMap { - - import dotty.tools.dotc.ast.tpd._ + class TailRecElimination(method: Symbol, enclosingClass: ClassSymbol, paramSyms: List[Symbol], isMandatory: Boolean) extends TreeMap { var rewrote: Boolean = false + var failureReported: Boolean = false /** The `tailLabelN` label symbol, used to encode a `continue` from the infinite `while` loop. */ private[this] var myContinueLabel: Symbol = _ - def continueLabel(implicit c: Context): Symbol = { + def continueLabel(implicit ctx: Context): Symbol = { if (myContinueLabel == null) - myContinueLabel = c.newSymbol(method, TailLabelName.fresh(), Flags.Label, defn.UnitType) + myContinueLabel = ctx.newSymbol(method, TailLabelName.fresh(), Label, defn.UnitType) myContinueLabel } @@ -209,23 +210,23 @@ class TailRec extends MiniPhase { /** The replacement `var`s for the params in `rewrittenParamSyms`. */ var varsForRewrittenParamSyms: List[Symbol] = Nil - private def getVarForRewrittenThis()(implicit c: Context): Symbol = { + private def getVarForRewrittenThis()(implicit ctx: Context): Symbol = { varForRewrittenThis match { case Some(sym) => sym case none => val tpe = - if (enclosingClass.is(Flags.Module)) enclosingClass.thisType - else enclosingClass.asClass.classInfo.selfType - val sym = c.newSymbol(method, nme.SELF, Flags.Synthetic | Flags.Mutable, tpe) + if (enclosingClass.is(Module)) enclosingClass.thisType + else enclosingClass.classInfo.selfType + val sym = ctx.newSymbol(method, nme.SELF, Synthetic | Mutable, tpe) varForRewrittenThis = Some(sym) sym } } - private def getVarForRewrittenParam(param: Symbol)(implicit c: Context): Symbol = { + private def getVarForRewrittenParam(param: Symbol)(implicit ctx: Context): Symbol = { rewrittenParamSyms.indexOf(param) match { case -1 => - val sym = c.newSymbol(method, TailLocalName.fresh(param.name.toTermName), Flags.Synthetic | Flags.Mutable, param.info) + val sym = ctx.newSymbol(method, TailLocalName.fresh(param.name.toTermName), Synthetic | Mutable, param.info) rewrittenParamSyms ::= param varsForRewrittenParamSyms ::= sym sym @@ -234,57 +235,75 @@ class TailRec extends MiniPhase { } /** Symbols of Labeled blocks that are in tail position. */ - private val tailPositionLabeledSyms = new collection.mutable.HashSet[Symbol]() + private val tailPositionLabeledSyms = new mutable.HashSet[Symbol]() - private[this] var ctx: TailContext = yesTailContext + private[this] var inTailPosition = true /** Rewrite this tree to contain no tail recursive calls */ - def transform(tree: Tree, nctx: TailContext)(implicit c: Context): Tree = { - if (ctx == nctx) transform(tree) + def transform(tree: Tree, tailPosition: Boolean)(implicit ctx: Context): Tree = { + if (inTailPosition == tailPosition) transform(tree) else { - val saved = ctx - ctx = nctx + val saved = inTailPosition + inTailPosition = tailPosition try transform(tree) - finally this.ctx = saved + finally inTailPosition = saved } } - def yesTailTransform(tree: Tree)(implicit c: Context): Tree = - transform(tree, yesTailContext) + def yesTailTransform(tree: Tree)(implicit ctx: Context): Tree = + transform(tree, tailPosition = true) + + /** If not in tail position a tree traversal may not be needed. + * + * A recursive call may still be in tail position if within the return + * expression of a labeled block. + * A tree traversal may also be needed to report a failure to transform + * a recursive call of a @tailrec annotated method (i.e. `isMandatory`). + */ + private def isTraversalNeeded = + isMandatory || tailPositionLabeledSyms.nonEmpty - def noTailTransform(tree: Tree)(implicit c: Context): Tree = - transform(tree, noTailContext) + def noTailTransform(tree: Tree)(implicit ctx: Context): Tree = + if (isTraversalNeeded) transform(tree, tailPosition = false) + else tree - def noTailTransforms[Tr <: Tree](trees: List[Tr])(implicit c: Context): List[Tr] = - trees.mapConserve(noTailTransform).asInstanceOf[List[Tr]] + def noTailTransforms[Tr <: Tree](trees: List[Tr])(implicit ctx: Context): List[Tr] = + if (isTraversalNeeded) trees.mapConserve(noTailTransform).asInstanceOf[List[Tr]] + else trees - override def transform(tree: Tree)(implicit c: Context): Tree = { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { /* Rewrite an Apply to be considered for tail call transformation. */ def rewriteApply(tree: Apply): Tree = { - val call = tree.fun - val sym = call.symbol val arguments = noTailTransforms(tree.args) - val prefix = call match { - case Select(qual, _) => qual - case x: Ident if x.symbol eq method => EmptyTree - case x => x - } - - val isRecursiveCall = (method eq sym) - def continue = - tpd.cpy.Apply(tree)(noTailTransform(call), arguments) + cpy.Apply(tree)(noTailTransform(tree.fun), arguments) def fail(reason: String) = { - if (isMandatory) c.error(s"Cannot rewrite recursive call: $reason", tree.pos) - else c.debuglog("Cannot rewrite recursive call at: " + tree.pos + " because: " + reason) + if (isMandatory) { + failureReported = true + ctx.error(s"Cannot rewrite recursive call: $reason", tree.pos) + } + else + tailrec.println("Cannot rewrite recursive call at: " + tree.pos + " because: " + reason) continue } + val calledMethod = tree.fun.symbol + val prefix = tree.fun match { + case Select(qual, _) => qual + case x: Ident if x.symbol eq method => EmptyTree + case x => x + } + + val isRecursiveCall = calledMethod eq method + def isRecursiveSuperCall = (method.name eq calledMethod.name) && + method.matches(calledMethod) && + enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias + if (isRecursiveCall) { - if (ctx.tailPos) { - c.debuglog("Rewriting tail recursive call: " + tree.pos) + if (inTailPosition) { + tailrec.println("Rewriting tail recursive call: " + tree.pos) rewrote = true val assignParamPairs = for { @@ -310,7 +329,7 @@ class TailRec extends MiniPhase { Assign(ref(lhs), rhs) :: Nil case _ :: _ => val (tempValDefs, assigns) = (for ((lhs, rhs) <- assignThisAndParamPairs) yield { - val temp = c.newSymbol(method, TailTempName.fresh(lhs.name.toTermName), Flags.Synthetic, lhs.info) + val temp = ctx.newSymbol(method, TailTempName.fresh(lhs.name.toTermName), Synthetic, lhs.info) (ValDef(temp, rhs), Assign(ref(lhs), ref(temp)).withPos(tree.pos)) }).unzip tempValDefs ::: assigns @@ -323,104 +342,96 @@ class TailRec extends MiniPhase { * which can cause Ycheck errors. */ val tpt = TypeTree(method.info.resultType) - seq(assignments, Typed(Return(Literal(Constant(())).withPos(tree.pos), continueLabel), tpt)) + seq(assignments, Typed(Return(unitLiteral.withPos(tree.pos), continueLabel), tpt)) } else fail("it is not in tail position") - } else { - // FIXME `(method.name eq sym)` is always false (Name vs Symbol). What is this trying to do? - val receiverIsSuper = (method.name eq sym) && enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias - - if (receiverIsSuper) fail("it contains a recursive call targeting a supertype") - else continue - } - } - - def rewriteTry(tree: Try): Try = { - if (tree.finalizer eq EmptyTree) { - // SI-1672 Catches are in tail position when there is no finalizer - tpd.cpy.Try(tree)( - noTailTransform(tree.expr), - transformSub(tree.cases), - EmptyTree - ) - } - else { - tpd.cpy.Try(tree)( - noTailTransform(tree.expr), - noTailTransforms(tree.cases), - noTailTransform(tree.finalizer) - ) } + else if (isRecursiveSuperCall) + fail("it targets a supertype") + else + continue } - val res: Tree = tree match { - case tree@Apply(fun, args) => + tree match { + case tree @ Apply(fun, args) => val meth = fun.symbol if (meth == defn.Boolean_|| || meth == defn.Boolean_&&) - tpd.cpy.Apply(tree)(noTailTransform(fun), transform(args)) + cpy.Apply(tree)(noTailTransform(fun), transform(args)) else rewriteApply(tree) - case tree: Select => - tpd.cpy.Select(tree)(noTailTransform(tree.qualifier), tree.name) + case tree @ Select(qual, name) => + cpy.Select(tree)(noTailTransform(qual), name) - case tree@Block(stats, expr) => - tpd.cpy.Block(tree)( + case tree @ Block(stats, expr) => + cpy.Block(tree)( noTailTransforms(stats), transform(expr) ) - case tree@If(cond, thenp, elsep) => - tpd.cpy.If(tree)( + case tree @ If(cond, thenp, elsep) => + cpy.If(tree)( noTailTransform(cond), transform(thenp), transform(elsep) ) - case tree@CaseDef(_, _, body) => - cpy.CaseDef(tree)(body = transform(body)) + case tree: CaseDef => + cpy.CaseDef(tree)(body = transform(tree.body)) - case tree@Match(selector, cases) => - tpd.cpy.Match(tree)( + case tree @ Match(selector, cases) => + cpy.Match(tree)( noTailTransform(selector), transformSub(cases) ) case tree: Try => - rewriteTry(tree) + val expr = noTailTransform(tree.expr) + if (tree.finalizer eq EmptyTree) { + // SI-1672 Catches are in tail position when there is no finalizer + cpy.Try(tree)(expr, transformSub(tree.cases), EmptyTree) + } + else cpy.Try(tree)( + expr, + noTailTransforms(tree.cases), + noTailTransform(tree.finalizer) + ) - case Alternative(_) | Bind(_, _) => + case tree @ WhileDo(cond, body) => + cpy.WhileDo(tree)( + noTailTransform(cond), + noTailTransform(body) + ) + + case _: Alternative | _: Bind => assert(false, "We should never have gotten inside a pattern") tree - case ValDef(_, _, _) | DefDef(_, _, _, _, _) | EmptyTree | Super(_, _) | This(_) | - Literal(_) | TypeTree() | TypeDef(_, _) => + case _: ValDef | _: DefDef | _: Super | _: This | + _: Literal | _: TypeTree | _: TypeDef | EmptyTree => tree case Labeled(bind, expr) => - if (ctx.tailPos) + if (inTailPosition) tailPositionLabeledSyms += bind.symbol - tpd.cpy.Labeled(tree)(bind, transform(expr)) + try cpy.Labeled(tree)(bind, transform(expr)) + finally { + if (inTailPosition) + tailPositionLabeledSyms -= bind.symbol + } case Return(expr, from) => val fromSym = from.symbol - val tailPos = fromSym.is(Flags.Label) && tailPositionLabeledSyms.contains(fromSym) - tpd.cpy.Return(tree)(transform(expr, new TailContext(tailPos)), from) + val inTailPosition = fromSym.is(Label) && tailPositionLabeledSyms.contains(fromSym) + cpy.Return(tree)(transform(expr, inTailPosition), from) case _ => super.transform(tree) } - - res } } } object TailRec { val name: String = "tailrec" - - final class TailContext(val tailPos: Boolean) extends AnyVal - - final val noTailContext: TailRec.TailContext = new TailContext(false) - final val yesTailContext: TailRec.TailContext = new TailContext(true) } diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 575f04a08c0b..4710e410c71e 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1507,8 +1507,8 @@ class ErrorMessagesTests extends ErrorMessagesTest { implicit val ctx: Context = ictx assertMessageCount(4, messages) - val tailRegMessages = messages.map({ case m: TailrecNotApplicable => m.symbolKind }).toSet - assertEquals(tailRegMessages, Set("variable", "value", "object", "class")) + val tailRecMessages = messages.map({ case TailrecNotApplicable(sym) => sym.showKind }).toSet + assertEquals(tailRecMessages, Set("variable", "value", "object", "class")) } @Test def notAnExtractor() = diff --git a/tests/neg-tailcall/t1672b.check b/tests/neg-tailcall/t1672b.check deleted file mode 100644 index 60ccf771742d..000000000000 --- a/tests/neg-tailcall/t1672b.check +++ /dev/null @@ -1,16 +0,0 @@ -t1672b.scala:3: error: could not optimize @tailrec annotated method bar: it contains a recursive call not in tail position - def bar : Nothing = { - ^ -t1672b.scala:14: error: could not optimize @tailrec annotated method baz: it contains a recursive call not in tail position - def baz : Nothing = { - ^ -t1672b.scala:29: error: could not optimize @tailrec annotated method boz: it contains a recursive call not in tail position - case _: Throwable => boz; ??? - ^ -t1672b.scala:34: error: could not optimize @tailrec annotated method bez: it contains a recursive call not in tail position - def bez : Nothing = { - ^ -t1672b.scala:46: error: could not optimize @tailrec annotated method bar: it contains a recursive call not in tail position - else 1 + (try { - ^ -5 errors found diff --git a/tests/neg-tailcall/t1672b.scala b/tests/neg-tailcall/t1672b.scala index 20185c430116..fa1b12c57cbd 100644 --- a/tests/neg-tailcall/t1672b.scala +++ b/tests/neg-tailcall/t1672b.scala @@ -1,6 +1,8 @@ +import annotation.tailrec + object Test1772B { - @annotation.tailrec - def bar : Nothing = { // error: TailRec optimisation not applicable + @tailrec + def bar : Nothing = { try { throw new RuntimeException } catch { @@ -10,8 +12,8 @@ object Test1772B { } } - @annotation.tailrec - def baz : Nothing = { // error: TailRec optimisation not applicable + @tailrec + def baz : Nothing = { try { throw new RuntimeException } catch { @@ -21,8 +23,8 @@ object Test1772B { } } - @annotation.tailrec - def boz : Nothing = { // error: TailRec optimisation not applicable + @tailrec + def boz : Nothing = { try { throw new RuntimeException } catch { @@ -30,8 +32,8 @@ object Test1772B { } } - @annotation.tailrec - def bez : Nothing = { // error: TailRec optimisation not applicable + @tailrec + def bez : Nothing = { try { bez // error: it is not in tail position } finally { @@ -40,7 +42,7 @@ object Test1772B { } // the `liftedTree` local method will prevent a tail call here. - @annotation.tailrec + @tailrec def bar(i : Int) : Int = { // error: TailRec optimisation not applicable if (i == 0) 0 else 1 + (try { diff --git a/tests/neg-tailcall/t3275.check b/tests/neg-tailcall/t3275.check deleted file mode 100644 index 117c792321e6..000000000000 --- a/tests/neg-tailcall/t3275.check +++ /dev/null @@ -1,4 +0,0 @@ -t3275.scala:2: error: @tailrec annotated method contains no recursive calls - @annotation.tailrec def foo() = 5 - ^ -one error found diff --git a/tests/neg-tailcall/t3275.scala b/tests/neg-tailcall/t3275.scala index df6155035952..8a648135bd8b 100644 --- a/tests/neg-tailcall/t3275.scala +++ b/tests/neg-tailcall/t3275.scala @@ -1,3 +1,3 @@ object Test { - @annotation.tailrec def foo() = 5 // error + @annotation.tailrec def foo() = 5 // error: not recursive } diff --git a/tests/neg-tailcall/t6574.check b/tests/neg-tailcall/t6574.check deleted file mode 100644 index c67b4ed80403..000000000000 --- a/tests/neg-tailcall/t6574.check +++ /dev/null @@ -1,7 +0,0 @@ -t6574.scala:4: error: could not optimize @tailrec annotated method notTailPos$extension: it contains a recursive call not in tail position - println("tail") - ^ -t6574.scala:8: error: could not optimize @tailrec annotated method differentTypeArgs$extension: it is called recursively with different type arguments - {(); new Bad[String, Unit](0)}.differentTypeArgs - ^ -two errors found diff --git a/tests/neg-tailcall/t6574.scala b/tests/neg-tailcall/t6574.scala index dfae77953234..d75440a21806 100644 --- a/tests/neg-tailcall/t6574.scala +++ b/tests/neg-tailcall/t6574.scala @@ -1,10 +1,12 @@ +import annotation.tailrec + class Bad[X, Y](val v: Int) extends AnyVal { - @annotation.tailrec final def notTailPos[Z](a: Int)(b: String): Unit = { // error - this.notTailPos[Z](a)(b) // error + @tailrec final def notTailPos[Z](a: Int)(b: String): Unit = { + this.notTailPos[Z](a)(b) // error: it is not in tail position println("tail") } - @annotation.tailrec final def differentTypeArgs: Unit = { + @tailrec final def differentTypeArgs: Unit = { {(); new Bad[String, Unit](0)}.differentTypeArgs } } diff --git a/tests/neg-tailcall/tailrec-2.check b/tests/neg-tailcall/tailrec-2.check deleted file mode 100644 index 1daad6922edc..000000000000 --- a/tests/neg-tailcall/tailrec-2.check +++ /dev/null @@ -1,7 +0,0 @@ -tailrec-2.scala:8: error: could not optimize @tailrec annotated method f: it contains a recursive call targeting a supertype - @annotation.tailrec final def f[B >: A](mem: List[B]): List[B] = (null: Super[A]).f(mem) - ^ -tailrec-2.scala:9: error: @tailrec annotated method contains no recursive calls - @annotation.tailrec final def f1[B >: A](mem: List[B]): List[B] = this.g(mem) - ^ -two errors found diff --git a/tests/neg-tailcall/tailrec-2.scala b/tests/neg-tailcall/tailrec-2.scala index b5edab4c701a..b6683251f0b0 100644 --- a/tests/neg-tailcall/tailrec-2.scala +++ b/tests/neg-tailcall/tailrec-2.scala @@ -1,3 +1,5 @@ +import annotation.tailrec + sealed abstract class Super[+A] { def f[B >: A](mem: List[B]) : List[B] def g(mem: List[_]) = ??? @@ -5,18 +7,20 @@ sealed abstract class Super[+A] { // This one should fail, target is a supertype class Bop1[+A](val element: A) extends Super[A] { - @annotation.tailrec final def f[B >: A](mem: List[B]): List[B] = (null: Super[A]).f(mem) - @annotation.tailrec final def f1[B >: A](mem: List[B]): List[B] = this.g(mem) + @tailrec final def f[B >: A](mem: List[B]): List[B] = + (null: Super[A]).f(mem) // error: recursive call targeting a supertype + + @tailrec final def f1[B >: A](mem: List[B]): List[B] = this.g(mem) // error: TailRec optimisation not applicable } // These succeed class Bop2[+A](val element: A) extends Super[A] { - @annotation.tailrec final def f[B >: A](mem: List[B]): List[B] = (null: Bop2[A]).f(mem) + @tailrec final def f[B >: A](mem: List[B]): List[B] = (null: Bop2[A]).f(mem) } object Bop3 extends Super[Nothing] { - @annotation.tailrec final def f[B](mem: List[B]): List[B] = (???: Bop3.type).f(mem) // error // error + @tailrec final def f[B](mem: List[B]): List[B] = (??? : Bop3.type).f(mem) } class Bop4[+A](val element: A) extends Super[A] { - @annotation.tailrec final def f[B >: A](mem: List[B]): List[B] = Other.f[A].f(mem) + @tailrec final def f[B >: A](mem: List[B]): List[B] = Other.f[A].f(mem) } object Other { diff --git a/tests/neg-tailcall/tailrec-3.check b/tests/neg-tailcall/tailrec-3.check deleted file mode 100644 index a3542fb56441..000000000000 --- a/tests/neg-tailcall/tailrec-3.check +++ /dev/null @@ -1,10 +0,0 @@ -tailrec-3.scala:4: error: could not optimize @tailrec annotated method quux: it contains a recursive call not in tail position - @tailrec private def quux(xs: List[String]): List[String] = quux(quux(xs)) - ^ -tailrec-3.scala:6: error: could not optimize @tailrec annotated method quux2: it contains a recursive call not in tail position - case x1 :: x2 :: rest => quux2(x1 :: quux2(rest)) - ^ -tailrec-3.scala:10: error: could not optimize @tailrec annotated method quux3: it contains a recursive call not in tail position - case x :: xs if quux3(List("abc")) => quux3(xs) - ^ -three errors found diff --git a/tests/neg-tailcall/tailrec-3.scala b/tests/neg-tailcall/tailrec-3.scala index 391f39c90dab..2ed3f17d7b65 100644 --- a/tests/neg-tailcall/tailrec-3.scala +++ b/tests/neg-tailcall/tailrec-3.scala @@ -1,14 +1,19 @@ + import annotation.tailrec object Test { - @tailrec private def quux(xs: List[String]): List[String] = quux(quux(xs)) // error + @tailrec private def quux(xs: List[String]): List[String] = + quux( + quux(xs) // error: not in tail position + ) @tailrec private def quux2(xs: List[String]): List[String] = xs match { - case x1 :: x2 :: rest => quux2(x1 :: quux2(rest)) // error - case _ => Nil + case x1 :: x2 :: rest => quux2( + x1 :: quux2(rest)) // error: not in tail position + case _ => Nil } @tailrec private def quux3(xs: List[String]): Boolean = xs match { - case x :: xs if quux3(List("abc")) => quux3(xs) // error - case _ => false + case x :: xs if quux3(List("abc")) => // error: not in tail position + quux3(xs) + case _ => false } } - diff --git a/tests/neg-tailcall/tailrec.check b/tests/neg-tailcall/tailrec.check deleted file mode 100644 index 946d3421e689..000000000000 --- a/tests/neg-tailcall/tailrec.check +++ /dev/null @@ -1,16 +0,0 @@ -tailrec.scala:45: error: could not optimize @tailrec annotated method facfail: it contains a recursive call not in tail position - else n * facfail(n - 1) - ^ -tailrec.scala:50: error: could not optimize @tailrec annotated method fail1: it is neither private nor final so can be overridden - @tailrec def fail1(x: Int): Int = fail1(x) - ^ -tailrec.scala:53: error: could not optimize @tailrec annotated method fail2: it contains a recursive call not in tail position - @tailrec final def fail2[T](xs: List[T]): List[T] = xs match { - ^ -tailrec.scala:59: error: could not optimize @tailrec annotated method fail3: it is called recursively with different type arguments - @tailrec final def fail3[T](x: Int): Int = fail3(x - 1) - ^ -tailrec.scala:63: error: could not optimize @tailrec annotated method fail4: it changes type of 'this' on a polymorphic recursive call - @tailrec final def fail4[U](other: Tom[U], x: Int): Int = other.fail4[U](other, x - 1) - ^ -5 errors found diff --git a/tests/neg-tailcall/tailrec.scala b/tests/neg-tailcall/tailrec.scala index 37219f031eb1..31cf27ec0614 100644 --- a/tests/neg-tailcall/tailrec.scala +++ b/tests/neg-tailcall/tailrec.scala @@ -47,18 +47,17 @@ class Winners { object Failures { @tailrec - def facfail(n: Int): Int = // error + def facfail(n: Int): Int = if (n == 0) 1 - else n * facfail(n - 1) // error + else n * facfail(n - 1) // error: not in tail pos } class Failures { - // not private, not final - @tailrec def fail1(x: Int): Int = fail1(x) // error + @tailrec def fail1(x: Int): Int = fail1(x) // error: not private, not final // a typical between-chair-and-keyboard error - @tailrec final def fail2[T](xs: List[T]): List[T] = xs match { // error + @tailrec final def fail2[T](xs: List[T]): List[T] = xs match { case Nil => Nil - case x :: xs => x :: fail2[T](xs) // error + case x :: xs => x :: fail2[T](xs) // error: not in tail pos } } diff --git a/tests/neg-tailcall/while-loops.scala b/tests/neg-tailcall/while-loops.scala new file mode 100644 index 000000000000..c7ed74606a30 --- /dev/null +++ b/tests/neg-tailcall/while-loops.scala @@ -0,0 +1,16 @@ +import annotation.tailrec + +object WhileLoops { + def cond: Boolean = ??? + + @tailrec def rec1: Unit = { + while (cond) { + rec1 // error: not in tail position + } + } + + @tailrec def rec2: Boolean = { + while (rec2) { } // error: not in tail position + true + } +} diff --git a/tests/neg/i4196.scala b/tests/neg/i4196.scala index ba197eaa2c5d..8a80807a3d5c 100644 --- a/tests/neg/i4196.scala +++ b/tests/neg/i4196.scala @@ -1,6 +1,6 @@ object Test { @annotation.tailrec - def foo(i: implicit Unit => Int): implicit Unit => Int = // error: method not tail recursive + def foo(i: implicit Unit => Int): implicit Unit => Int = if (i == 0) 0 else diff --git a/tests/pos/tailcall/i5163.scala b/tests/pos/tailcall/i5163.scala new file mode 100644 index 000000000000..e15973c9eeb2 --- /dev/null +++ b/tests/pos/tailcall/i5163.scala @@ -0,0 +1,10 @@ +import annotation.tailrec + +class UnrolledBuffer { + def remove(idx: Int): Unit = () + @tailrec final def remove(idx: Int, count: Int): Unit = + if (count > 0) { + remove(idx) // ok: not a recursive call + remove(idx, count - 1) + } +} diff --git a/tests/pos/tailcall/t4649.flags b/tests/pos/tailcall/t4649.flags deleted file mode 100644 index e8fb65d50c20..000000000000 --- a/tests/pos/tailcall/t4649.flags +++ /dev/null @@ -1 +0,0 @@ --Xfatal-warnings \ No newline at end of file diff --git a/tests/pos/tailcall/t4649.scala b/tests/pos/tailcall/t4649.scala index 5f009b7a496e..12ebe095894b 100644 --- a/tests/pos/tailcall/t4649.scala +++ b/tests/pos/tailcall/t4649.scala @@ -1,6 +1,29 @@ +import annotation.tailrec + object Test4649 { - // @annotation.tailrec + @tailrec def lazyFilter[E](s: Stream[E], p: E => Boolean): Stream[E] = s match { - case h #:: t => if (p(h)) h #:: lazyFilter(t, p) else lazyFilter(t, p) + case h #:: t => + if (p(h)) h #:: lazyFilter(t, p) // Ok: not a tail call but in a closure + else lazyFilter(t, p) + case _ => + Stream.empty + } + + def cond: Boolean = ??? + def foo(x: => Int): Int = ??? + + @tailrec + def bar: Int = { + if (cond) foo(bar) // Ok: not a tail call but in a closure + else bar + } + + def foo2(x: Int => Int) = ??? + + @tailrec + def bar2: Int = { + if (cond) foo2(_ => bar2) // Ok: not a tail call but in a closure + else bar2 } } diff --git a/tests/pos/tailcall/t6891.flags b/tests/pos/tailcall/t6891.flags deleted file mode 100644 index fe048006aa8d..000000000000 --- a/tests/pos/tailcall/t6891.flags +++ /dev/null @@ -1 +0,0 @@ --Ycheck:extmethods -Xfatal-warnings \ No newline at end of file diff --git a/tests/pos/tailcall/t6891.scala b/tests/pos/tailcall/t6891.scala index edbe6f097a6b..fe7dd0e7fba8 100644 --- a/tests/pos/tailcall/t6891.scala +++ b/tests/pos/tailcall/t6891.scala @@ -1,26 +1,22 @@ +import annotation.tailrec + object O6891 { implicit class Foo[A](val value: String) extends AnyVal { def bippy() = { - @annotation.tailrec def loop(x: A): Unit = loop(x) + @tailrec def loop(x: A): Unit = loop(x) () } def boppy() = { - @annotation.tailrec def loop(x: value.type): Unit = loop(x) + @tailrec def loop(x: value.type): Unit = loop(x) () } def beppy[C](c: => C) = { () => c - @annotation.tailrec def loop(x: value.type): Unit = loop(x) + @tailrec def loop(x: value.type): Unit = loop(x) () => c () } } - // uncaught exception during compilation: Types$TypeError("type mismatch; - // found : A(in method bippy$extension) - // required: A(in class Foo)") @ scala.tools.nsc.typechecker.Contexts$Context.issueCommon(Contexts.scala:396) - // error: scala.reflect.internal.Types$TypeError: type mismatch; - // found : A(in method bippy$extension) - // required: A(in class Foo) }