From 277a20e50b6210da8dcf6a78db14fa08b4d70230 Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Wed, 26 Sep 2018 16:26:39 +0200 Subject: [PATCH 1/2] Cleanup TailRec --- .../dotty/tools/dotc/config/Printers.scala | 1 + .../dotc/reporting/diagnostic/messages.scala | 2 +- .../dotty/tools/dotc/transform/TailRec.scala | 279 ++++++++---------- 3 files changed, 131 insertions(+), 151 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index bd8cb9844c0c..8501470b5db5 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -31,6 +31,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 59dc71e76516..87ced50339dc 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1804,7 +1804,7 @@ object messages { val symbolKind: String = symbol.showKind val msg: String = if (symbol.is(Method)) - hl"TailRec optimisation not applicable, $symbol is neither ${"private"} nor ${"final"}." + hl"TailRec optimisation not applicable, method not tail recursive" else hl"TailRec optimisation not applicable, ${symbolKind} isn't a method." val explanation: String = diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index bde3d80b98ce..6305162907af 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -4,11 +4,13 @@ package transform import ast.Trees._ import ast.{TreeTypeMap, tpd} import core._ +import Flags._ import Contexts.Context import Decorators._ import Symbols._ import StdNames.nme import Types._ +import config.Printers.tailrec import NameKinds.TailLabelName import MegaPhase.MiniPhase import reporting.diagnostic.messages.TailrecNotApplicable @@ -64,15 +66,13 @@ import reporting.diagnostic.messages.TailrecNotApplicable *

*/ 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 - final val labelFlags: Flags.FlagSet = Flags.Synthetic | Flags.Label | Flags.Method + final val labelFlags: FlagSet = Synthetic | Label | Method private def mkLabel(method: Symbol)(implicit ctx: Context): TermSymbol = { val name = TailLabelName.fresh() @@ -82,7 +82,7 @@ class TailRec extends MiniPhase { val enclosingClass = method.enclosingClass.asClass val thisParamType = - if (enclosingClass.is(Flags.Module)) enclosingClass.thisType + if (enclosingClass.is(Module)) enclosingClass.thisType else enclosingClass.classInfo.selfType ctx.newSymbol(method, name.toTermName, labelFlags, @@ -92,161 +92,152 @@ class TailRec extends MiniPhase { } 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) || (sym is Flags.Label)) => - val mandatory = sym.hasAnnotation(defn.TailrecAnnot) - cpy.DefDef(dd)(rhs = { - val defIsTopLevel = sym.owner.isClass - val origMeth = sym - val label = mkLabel(sym) - val owner = ctx.owner.enclosingClass.asClass - - var rewrote = false - - // 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 rhsSemiTransformed = { - val transformer = new TailRecElimination(origMeth, owner, mandatory, label) - val rhs = transformer.transform(dd.rhs) - rewrote = transformer.rewrote - rhs - } - - if (rewrote) { - if (tree.symbol.owner.isClass) { - val classSym = tree.symbol.owner.asClass - - val labelDef = DefDef(label, vrefss => { - assert(vrefss.size == 1, vrefss) - val vrefs = vrefss.head - val thisRef = vrefs.head - val origMeth = tree.symbol - val origVParams = vparams.map(_.symbol) - new TreeTypeMap( - typeMap = identity(_) - .substThisUnlessStatic(classSym, thisRef.tpe) - .subst(origVParams, vrefs.tail.map(_.tpe)), - treeMap = { - case tree: This if tree.symbol == classSym => thisRef - case tree => tree - }, - oldOwners = origMeth :: Nil, - newOwners = label :: Nil - ).transform(rhsSemiTransformed) - }) - val callIntoLabel = ref(label).appliedToArgs(This(classSym) :: vparams.map(x => ref(x.symbol))) - Block(List(labelDef), callIntoLabel) - } else { // inner method. Tail recursion does not change `this` - val labelDef = DefDef(label, vrefss => { - assert(vrefss.size == 1, vrefss) - val vrefs = vrefss.head - val origMeth = tree.symbol - val origVParams = vparams.map(_.symbol) - new TreeTypeMap( - typeMap = identity(_) - .subst(origVParams, vrefs.map(_.tpe)), - oldOwners = origMeth :: Nil, - newOwners = label :: Nil - ).transform(rhsSemiTransformed) - }) - val callIntoLabel = ref(label).appliedToArgs(vparams.map(x => ref(x.symbol))) - Block(List(labelDef), callIntoLabel) - }} 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 + val method = tree.symbol + val mandatory = method.hasAnnotation(defn.TailrecAnnot) + def noTailTransform = { + // FIXME: want to report this error on `tree.namePos`, but + // because of extension method getting a weird pos, it is + // better to report on methodbol so there's no overlap + if (mandatory) + ctx.error(TailrecNotApplicable(method), method.pos) + tree } + val isCandidate = method.isEffectivelyFinal && + !((method is Accessor) || (tree.rhs eq EmptyTree) || (method is Label)) + + if (isCandidate) { + val label = mkLabel(method) + + // 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, mandatory, label) + val rhsSemiTransformed = transformer.transform(tree.rhs) + + if (transformer.rewrote) { + val DefDef(name, Nil, vparams :: Nil, _, _) = tree + val origVParams = vparams.map(_.symbol) + val origVParamRefs = origVParams.map(ref(_)) + + if (method.owner.isClass) { + val classSym = tree.symbol.owner.asClass + + val labelDef = DefDef(label, vrefss => { + assert(vrefss.size == 1, vrefss) + val vrefs = vrefss.head + val thisRef = vrefs.head + + new TreeTypeMap( + typeMap = identity(_) + .substThisUnlessStatic(classSym, thisRef.tpe) + .subst(origVParams, vrefs.tail.map(_.tpe)), + treeMap = { + case tree: This if tree.symbol == classSym => thisRef + case tree => tree + }, + oldOwners = method :: Nil, + newOwners = label :: Nil + ).transform(rhsSemiTransformed) + }) + val callIntoLabel = ref(label).appliedToArgs(This(classSym) :: origVParamRefs) + cpy.DefDef(tree)(rhs = Block(List(labelDef), callIntoLabel)) + } else { + // Inner methods: Tail recursion does not change `this` + val labelDef = DefDef(label, vrefss => { + assert(vrefss.size == 1, vrefss) + val vrefs = vrefss.head + + new TreeTypeMap( + typeMap = identity(_) + .subst(origVParams, vrefs.map(_.tpe)), + oldOwners = method :: Nil, + newOwners = label :: Nil + ).transform(rhsSemiTransformed) + }) + val callIntoLabel = ref(label).appliedToArgs(origVParamRefs) + cpy.DefDef(tree)(rhs = Block(List(labelDef), callIntoLabel)) + } + } + else noTailTransform + } + else noTailTransform } - class TailRecElimination(method: Symbol, enclosingClass: Symbol, isMandatory: Boolean, label: Symbol) extends tpd.TreeMap { - - import dotty.tools.dotc.ast.tpd._ + private class TailRecElimination(method: Symbol, isMandatory: Boolean, label: Symbol) extends tpd.TreeMap { + import tpd._ var rewrote: Boolean = false /** Symbols of Labeled blocks that are in tail position. */ private val tailPositionLabeledSyms = new collection.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) - def noTailTransform(tree: Tree)(implicit c: Context): Tree = - transform(tree, noTailContext) + def noTailTransform(tree: Tree)(implicit ctx: Context): Tree = + transform(tree, tailPosition = false) - def noTailTransforms[Tr <: Tree](trees: List[Tr])(implicit c: Context): List[Tr] = + def noTailTransforms[Tr <: Tree](trees: List[Tr])(implicit ctx: Context): List[Tr] = trees.mapConserve(noTailTransform).asInstanceOf[List[Tr]] - 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) ctx.error(s"Cannot rewrite recursive call: $reason", tree.pos) + else tailrec.println("Cannot rewrite recursive call at: " + tree.pos + " because: " + reason) continue } + def enclosingClass = method.enclosingClass.asClass + + val call = 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 = call eq method + 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 + def receiver = - if (prefix eq EmptyTree) This(enclosingClass.asClass) + if (prefix eq EmptyTree) This(enclosingClass) else noTailTransform(prefix) val argumentsWithReceiver = - if (this.method.owner.isClass) receiver :: arguments + if (method.owner.isClass) receiver :: arguments else arguments - tpd.cpy.Apply(tree)(ref(label), argumentsWithReceiver) + cpy.Apply(tree)(ref(label), argumentsWithReceiver) } 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 + // FIXME `(method.name eq call)` is always false (Name vs Symbol). What is this trying to do? + val receiverIsSuper = (method.name eq call) && enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias if (receiverIsSuper) fail("it contains a recursive call targeting a supertype") else continue @@ -254,42 +245,37 @@ class TailRec extends MiniPhase { } 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) - ) - } + val expr = noTailTransform(tree.expr) + val hasFinalizer = tree.finalizer ne EmptyTree + // SI-1672 Catches are in tail position when there is no finalizer + val cases = + if (hasFinalizer) noTailTransforms(tree.cases) + else transformSub(tree.cases) + val finalizer = + if (hasFinalizer) noTailTransform(tree.finalizer) + else EmptyTree + cpy.Try(tree)(expr, cases, finalizer) } - val res: Tree = tree match { + 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) + cpy.Select(tree)(noTailTransform(tree.qualifier), tree.name) case tree@Block(stats, expr) => - tpd.cpy.Block(tree)( + cpy.Block(tree)( noTailTransforms(stats), transform(expr) ) case tree@If(cond, thenp, elsep) => - tpd.cpy.If(tree)( + cpy.If(tree)( noTailTransform(cond), transform(thenp), transform(elsep) @@ -299,7 +285,7 @@ class TailRec extends MiniPhase { cpy.CaseDef(tree)(body = transform(body)) case tree@Match(selector, cases) => - tpd.cpy.Match(tree)( + cpy.Match(tree)( noTailTransform(selector), transformSub(cases) ) @@ -319,29 +305,22 @@ class TailRec extends MiniPhase { tree case Labeled(bind, expr) => - if (ctx.tailPos) + if (inTailPosition) tailPositionLabeledSyms += bind.symbol - tpd.cpy.Labeled(tree)(bind, transform(expr)) + cpy.Labeled(tree)(bind, transform(expr)) 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) } From 3dda9e56352d2cdaa8ac956f90d9ee829af1d9f9 Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Wed, 26 Sep 2018 19:26:45 +0200 Subject: [PATCH 2/2] Fix #5163: Properly report tailrec failures on recursive call targeting a supertype --- .../src/dotty/tools/dotc/transform/TailRec.scala | 14 ++++++++------ tests/neg-tailcall/t1672b.check | 16 ---------------- tests/neg-tailcall/t1672b.scala | 12 +++++++----- tests/neg-tailcall/t3275.check | 4 ---- tests/neg-tailcall/t3275.scala | 2 +- tests/neg-tailcall/t6574.check | 7 ------- tests/neg-tailcall/t6574.scala | 8 +++++--- tests/neg-tailcall/tailrec-2.check | 7 ------- tests/neg-tailcall/tailrec-2.scala | 14 +++++++++----- tests/neg-tailcall/tailrec-3.check | 10 ---------- tests/neg-tailcall/tailrec-3.scala | 15 ++++++++++----- tests/neg-tailcall/tailrec.check | 16 ---------------- tests/pos/tailcall/i5163.scala | 11 +++++++++++ 13 files changed, 51 insertions(+), 85 deletions(-) delete mode 100644 tests/neg-tailcall/t1672b.check delete mode 100644 tests/neg-tailcall/t3275.check delete mode 100644 tests/neg-tailcall/t6574.check delete mode 100644 tests/neg-tailcall/tailrec-2.check delete mode 100644 tests/neg-tailcall/tailrec-3.check delete mode 100644 tests/neg-tailcall/tailrec.check create mode 100644 tests/pos/tailcall/i5163.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 6305162907af..ebeaa681ec5c 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -217,7 +217,11 @@ class TailRec extends MiniPhase { case x: Ident if x.symbol eq method => EmptyTree case x => x } + val isRecursiveCall = call eq method + def isRecursiveSuperCall = (method.name eq call.name) && + method.matches(call) && + enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias if (isRecursiveCall) { if (inTailPosition) { @@ -235,13 +239,11 @@ class TailRec extends MiniPhase { cpy.Apply(tree)(ref(label), argumentsWithReceiver) } else fail("it is not in tail position") - } else { - // FIXME `(method.name eq call)` is always false (Name vs Symbol). What is this trying to do? - val receiverIsSuper = (method.name eq call) && enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias - - if (receiverIsSuper) fail("it contains a recursive call targeting a supertype") - else continue } + else if (isRecursiveSuperCall) + fail("it contains a recursive call targeting a supertype") + else + continue } def rewriteTry(tree: Try): Try = { 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..8b147ccf316c 100644 --- a/tests/neg-tailcall/t1672b.scala +++ b/tests/neg-tailcall/t1672b.scala @@ -1,5 +1,7 @@ +import annotation.tailrec + object Test1772B { - @annotation.tailrec + @tailrec def bar : Nothing = { // error: TailRec optimisation not applicable try { throw new RuntimeException @@ -10,7 +12,7 @@ object Test1772B { } } - @annotation.tailrec + @tailrec def baz : Nothing = { // error: TailRec optimisation not applicable try { throw new RuntimeException @@ -21,7 +23,7 @@ object Test1772B { } } - @annotation.tailrec + @tailrec def boz : Nothing = { // error: TailRec optimisation not applicable try { throw new RuntimeException @@ -30,7 +32,7 @@ object Test1772B { } } - @annotation.tailrec + @tailrec def bez : Nothing = { // error: TailRec optimisation not applicable try { bez // error: it is not in tail position @@ -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..96a3f9525321 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 = { // error: TailRec optimisation not applicable + 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..d9c9cdd8e978 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] = // error: TailRec optimisation not applicable + (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..ad76cf250960 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/pos/tailcall/i5163.scala b/tests/pos/tailcall/i5163.scala new file mode 100644 index 000000000000..2ce694096f3b --- /dev/null +++ b/tests/pos/tailcall/i5163.scala @@ -0,0 +1,11 @@ +import annotation.tailrec + +class UnrolledBuffer { + def remove(idx: Int): Unit = () + + @tailrec final def remove(idx: Int, count: Int): Unit = + if (count > 0) { + remove(idx) + remove(idx, count - 1) + } +}