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)
+ }
+}