Skip to content

Commit a129cbd

Browse files
committed
Improve error reporting for Tailrec
1 parent d47ead5 commit a129cbd

File tree

8 files changed

+40
-30
lines changed

8 files changed

+40
-30
lines changed

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,13 +1801,16 @@ object messages {
18011801
case class TailrecNotApplicable(symbol: Symbol)(implicit ctx: Context)
18021802
extends Message(TailrecNotApplicableID) {
18031803
val kind: String = "Syntax"
1804-
val msg: String =
1805-
if (symbol.is(Method))
1806-
"TailRec optimisation not applicable, method not tail recursive"
1807-
else
1808-
hl"TailRec optimisation not applicable, ${symbol.showKind} isn't a method."
1809-
val explanation: String =
1810-
hl"A method annotated ${"@tailrec"} must be declared ${"private"} or ${"final"} so it can't be overridden."
1804+
val msg: String = {
1805+
val reason =
1806+
if (!symbol.is(Method)) hl"$symbol isn't a method"
1807+
else if (symbol.is(Deferred)) hl"$symbol is abstract"
1808+
else if (!symbol.isEffectivelyFinal) hl"$symbol is neither ${"private"} nor ${"final"} so can be overridden"
1809+
else hl"$symbol contains no recursive calls"
1810+
1811+
s"TailRec optimisation not applicable, $reason"
1812+
}
1813+
val explanation: String = ""
18111814
}
18121815

18131816
case class FailureToEliminateExistential(tp: Type, tp1: Type, tp2: Type, boundSyms: List[Symbol])(implicit ctx: Context)

compiler/src/dotty/tools/dotc/transform/TailRec.scala

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,15 @@ class TailRec extends MiniPhase {
116116
override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = {
117117
val method = tree.symbol
118118
val mandatory = method.hasAnnotation(defn.TailrecAnnot)
119-
def noTailTransform = {
119+
def noTailTransform(failureReported: Boolean) = {
120120
// FIXME: want to report this error on `tree.namePos`, but
121121
// because of extension method getting a weird pos, it is
122-
// better to report on method symbol so there's no overlap
123-
if (mandatory)
122+
// better to report on method symbol so there's no overlap.
123+
// We don't report a new error if failures were reported
124+
// during the transformation.
125+
if (mandatory && !failureReported)
124126
ctx.error(TailrecNotApplicable(method), method.pos)
127+
125128
tree
126129
}
127130

@@ -182,14 +185,15 @@ class TailRec extends MiniPhase {
182185
)
183186
)
184187
}
185-
else noTailTransform
188+
else noTailTransform(failureReported = transformer.failureReported)
186189
}
187-
else noTailTransform
190+
else noTailTransform(failureReported = false)
188191
}
189192

190193
class TailRecElimination(method: Symbol, enclosingClass: ClassSymbol, paramSyms: List[Symbol], isMandatory: Boolean) extends TreeMap {
191194

192195
var rewrote: Boolean = false
196+
var failureReported: Boolean = false
193197

194198
/** The `tailLabelN` label symbol, used to encode a `continue` from the infinite `while` loop. */
195199
private[this] var myContinueLabel: Symbol = _
@@ -276,8 +280,12 @@ class TailRec extends MiniPhase {
276280
cpy.Apply(tree)(noTailTransform(tree.fun), arguments)
277281

278282
def fail(reason: String) = {
279-
if (isMandatory) ctx.error(s"Cannot rewrite recursive call: $reason", tree.pos)
280-
else tailrec.println("Cannot rewrite recursive call at: " + tree.pos + " because: " + reason)
283+
if (isMandatory) {
284+
failureReported = true
285+
ctx.error(s"Cannot rewrite recursive call: $reason", tree.pos)
286+
}
287+
else
288+
tailrec.println("Cannot rewrite recursive call at: " + tree.pos + " because: " + reason)
281289
continue
282290
}
283291

@@ -339,7 +347,7 @@ class TailRec extends MiniPhase {
339347
else fail("it is not in tail position")
340348
}
341349
else if (isRecursiveSuperCall)
342-
fail("it contains a recursive call targeting a supertype")
350+
fail("it targets a supertype")
343351
else
344352
continue
345353
}

tests/neg-tailcall/t1672b.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import annotation.tailrec
22

33
object Test1772B {
44
@tailrec
5-
def bar : Nothing = { // error: TailRec optimisation not applicable
5+
def bar : Nothing = {
66
try {
77
throw new RuntimeException
88
} catch {
@@ -13,7 +13,7 @@ object Test1772B {
1313
}
1414

1515
@tailrec
16-
def baz : Nothing = { // error: TailRec optimisation not applicable
16+
def baz : Nothing = {
1717
try {
1818
throw new RuntimeException
1919
} catch {
@@ -24,7 +24,7 @@ object Test1772B {
2424
}
2525

2626
@tailrec
27-
def boz : Nothing = { // error: TailRec optimisation not applicable
27+
def boz : Nothing = {
2828
try {
2929
throw new RuntimeException
3030
} catch {
@@ -33,7 +33,7 @@ object Test1772B {
3333
}
3434

3535
@tailrec
36-
def bez : Nothing = { // error: TailRec optimisation not applicable
36+
def bez : Nothing = {
3737
try {
3838
bez // error: it is not in tail position
3939
} finally {

tests/neg-tailcall/t6574.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import annotation.tailrec
22

33
class Bad[X, Y](val v: Int) extends AnyVal {
4-
@tailrec final def notTailPos[Z](a: Int)(b: String): Unit = { // error: TailRec optimisation not applicable
4+
@tailrec final def notTailPos[Z](a: Int)(b: String): Unit = {
55
this.notTailPos[Z](a)(b) // error: it is not in tail position
66
println("tail")
77
}

tests/neg-tailcall/tailrec-2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ sealed abstract class Super[+A] {
77
// This one should fail, target is a supertype
88
class Bop1[+A](val element: A) extends Super[A] {
99

10-
@tailrec final def f[B >: A](mem: List[B]): List[B] = // error: TailRec optimisation not applicable
10+
@tailrec final def f[B >: A](mem: List[B]): List[B] =
1111
(null: Super[A]).f(mem) // error: recursive call targeting a supertype
1212

1313
@tailrec final def f1[B >: A](mem: List[B]): List[B] = this.g(mem) // error: TailRec optimisation not applicable

tests/neg-tailcall/tailrec.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,17 @@ class Winners {
4747

4848
object Failures {
4949
@tailrec
50-
def facfail(n: Int): Int = // error
50+
def facfail(n: Int): Int =
5151
if (n == 0) 1
52-
else n * facfail(n - 1) // error
52+
else n * facfail(n - 1) // error: not in tail pos
5353
}
5454

5555
class Failures {
56-
// not private, not final
57-
@tailrec def fail1(x: Int): Int = fail1(x) // error
56+
@tailrec def fail1(x: Int): Int = fail1(x) // error: not private, not final
5857

5958
// a typical between-chair-and-keyboard error
60-
@tailrec final def fail2[T](xs: List[T]): List[T] = xs match { // error
59+
@tailrec final def fail2[T](xs: List[T]): List[T] = xs match {
6160
case Nil => Nil
62-
case x :: xs => x :: fail2[T](xs) // error
61+
case x :: xs => x :: fail2[T](xs) // error: not in tail pos
6362
}
6463
}

tests/neg-tailcall/while-loops.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import annotation.tailrec
33
object WhileLoops {
44
def cond: Boolean = ???
55

6-
@tailrec def rec1: Unit = { // error: tailrec not applicable
6+
@tailrec def rec1: Unit = {
77
while (cond) {
88
rec1 // error: not in tail position
99
}
1010
}
1111

12-
@tailrec def rec2: Boolean = { // error: tailrec not applicable
12+
@tailrec def rec2: Boolean = {
1313
while (rec2) { } // error: not in tail position
1414
true
1515
}

tests/neg/i4196.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object Test {
22
@annotation.tailrec
3-
def foo(i: implicit Unit => Int): implicit Unit => Int = // error: method not tail recursive
3+
def foo(i: implicit Unit => Int): implicit Unit => Int =
44
if (i == 0)
55
0
66
else

0 commit comments

Comments
 (0)