Skip to content

Commit 7d67f44

Browse files
author
Lucy Martin
committed
Preventing compilation of a @tailrec method when it does not rewrite, but an inner method does
Adding warnings if there is an annotated def at the top level that is referenced from an inner def
1 parent 73882c5 commit 7d67f44

File tree

5 files changed

+63
-3
lines changed

5 files changed

+63
-3
lines changed

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
207207
case MatchTypeLegacyPatternID // errorNumber: 191
208208
case UnstableInlineAccessorID // errorNumber: 192
209209
case VolatileOnValID // errorNumber: 193
210+
211+
case TailrecNestedCallID //errorNumber: 194
210212

211213
def errorNumber = ordinal - 1
212214

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1907,6 +1907,20 @@ class TailrecNotApplicable(symbol: Symbol)(using Context)
19071907
def explain(using Context) = ""
19081908
}
19091909

1910+
class TailrecNestedCall(definition: Symbol, innerDef: Symbol)(using Context)
1911+
extends SyntaxMsg(TailrecNestedCallID) {
1912+
def msg(using Context) = {
1913+
s"The tail recursive def ${definition.name} contains a recursive call inside the non-inlined inner def ${innerDef.name}"
1914+
}
1915+
1916+
def explain(using Context) =
1917+
"""Tail recursion is only validated and optimised directly in the definition
1918+
|any calls to the recursive method via an inner def cannot be validated as
1919+
|tail recursive, nor optimised if they are. To enable tail recursion from
1920+
|inner calls, mark the inner def as inline
1921+
|""".stripMargin
1922+
}
1923+
19101924
class FailureToEliminateExistential(tp: Type, tp1: Type, tp2: Type, boundSyms: List[Symbol], classRoot: Symbol)(using Context)
19111925
extends Message(FailureToEliminateExistentialID) {
19121926
def kind = MessageKind.Compatibility

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ package transform
44
import ast.{TreeTypeMap, tpd}
55
import config.Printers.tailrec
66
import core.*
7-
import Contexts.*, Flags.*, Symbols.*, Decorators.em
7+
import Contexts.*
8+
import Flags.*
9+
import Symbols.*
10+
import Decorators.em
811
import Constants.Constant
912
import NameKinds.{TailLabelName, TailLocalName, TailTempName}
1013
import StdNames.nme
@@ -429,10 +432,20 @@ class TailRec extends MiniPhase {
429432
assert(false, "We should never have gotten inside a pattern")
430433
tree
431434

432-
case tree: ValOrDefDef =>
435+
case tree: ValDef =>
433436
if (isMandatory) noTailTransform(tree.rhs)
434437
tree
435438

439+
case tree: DefDef =>
440+
if (isMandatory)
441+
// We cant tail recurse through nested definitions, so dont want to propagate to child nodes
442+
// We dont want to fail if there is a call that would recurse (as this would be a non self recurse), so dont
443+
// want to call noTailTransform
444+
// We can however warn in this case, as its likely in this situation that someone would expect a tail
445+
// recursion optimization and enabling this to optimise would be a simple case of inlining the inner method
446+
new NestedTailRecAlerter(method, tree.symbol).traverse(tree)
447+
tree
448+
436449
case _: Super | _: This | _: Literal | _: TypeTree | _: TypeDef | EmptyTree =>
437450
tree
438451

@@ -446,14 +459,28 @@ class TailRec extends MiniPhase {
446459

447460
case Return(expr, from) =>
448461
val fromSym = from.symbol
449-
val inTailPosition = !fromSym.is(Label) || tailPositionLabeledSyms.contains(fromSym)
462+
val inTailPosition = (!fromSym.is(Label) || tailPositionLabeledSyms.contains(fromSym)) // Label returns are only tail if the label is in tail position
463+
&& (!fromSym.is(Method) || (fromSym eq method)) // Method returns are only tail if we are looking at the original method
450464
cpy.Return(tree)(transform(expr, inTailPosition), from)
451465

452466
case _ =>
453467
super.transform(tree)
454468
}
455469
}
456470
}
471+
472+
class NestedTailRecAlerter(method: Symbol, inner: Symbol) extends TreeTraverser {
473+
override def traverse(tree: tpd.Tree)(using Context): Unit =
474+
tree match {
475+
case a: Apply =>
476+
if (a.fun.symbol eq method) {
477+
report.warning(new TailrecNestedCall(method, inner), a.srcPos)
478+
}
479+
traverseChildren(tree)
480+
case _ =>
481+
traverseChildren(tree)
482+
}
483+
}
457484
}
458485

459486
object TailRec {

tests/neg/i20105.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@tailrec
2+
def foo(): Unit =
3+
def bar(): Unit =
4+
if (???)
5+
foo()
6+
else
7+
bar()
8+
bar()

tests/warn/i20105.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@tailrec
2+
def foo(): Unit =
3+
def bar(): Unit =
4+
if (???)
5+
foo() // warn
6+
else
7+
bar()
8+
bar()
9+
foo()

0 commit comments

Comments
 (0)