Skip to content

Commit ddf73dd

Browse files
committed
Fix #7821: Warn on simple infinite tail rec loops
1 parent 8305bd0 commit ddf73dd

File tree

3 files changed

+54
-0
lines changed

3 files changed

+54
-0
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import dotty.tools.dotc.ast.Trees._
55
import dotty.tools.dotc.ast.{TreeTypeMap, tpd}
66
import dotty.tools.dotc.config.Printers.tailrec
77
import dotty.tools.dotc.core.Contexts.Context
8+
import dotty.tools.dotc.core.Constants.Constant
89
import dotty.tools.dotc.core.Decorators._
910
import dotty.tools.dotc.core.Flags._
1011
import dotty.tools.dotc.core.NameKinds.{TailLabelName, TailLocalName, TailTempName}
@@ -174,6 +175,25 @@ class TailRec extends MiniPhase {
174175
).transform(rhsSemiTransformed)
175176
}
176177

178+
// Is a simple this a simple recursive tail call, possibly with swapped or modified pure arguments
179+
def isInfiniteRecCall(tree: Tree): Boolean = {
180+
def statOk(stat: Tree): Boolean = stat match {
181+
case stat: ValDef if stat.name.is(TailTempName) || !stat.symbol.is(Mutable) => statOk(stat.rhs)
182+
case Assign(lhs: Ident, rhs: Ident) if lhs.symbol.name.is(TailLocalName) => statOk(rhs)
183+
case stat: Ident if stat.symbol.name.is(TailLocalName) => true
184+
case _ => tpd.isPureExpr(stat)
185+
}
186+
tree match {
187+
case Typed(expr, _) => isInfiniteRecCall(expr)
188+
case Return(Literal(Constant(())), label) => label.symbol == transformer.continueLabel
189+
case Block(stats, expr) => stats.forall(statOk) && isInfiniteRecCall(expr)
190+
case _ => false
191+
}
192+
}
193+
194+
if isInfiniteRecCall(rhsFullyTransformed) then
195+
ctx.warning("Infinite recursive call", tree.sourcePos)
196+
177197
cpy.DefDef(tree)(rhs =
178198
Block(
179199
initialVarDefs,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object XObject {
2+
opaque type X = Int
3+
4+
def anX: X = 5
5+
6+
given ops: Object {
7+
def (x: X) + (y: X): X = x + y
8+
}
9+
}
10+
11+
object MyXObject {
12+
opaque type MyX = XObject.X
13+
14+
def anX: MyX = XObject.anX
15+
16+
given ops: Object {
17+
def (x: MyX) + (y: MyX): MyX = x + y // error: warring: Infinite recursive call
18+
}
19+
}
20+
21+
object Main extends App {
22+
println(XObject.anX + XObject.anX) // prints 10
23+
println(MyXObject.anX + MyXObject.anX) // infinite loop
24+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object Test {
2+
3+
{ def f(x: Int, y: Int): Int = f(x, y) } // error
4+
{ def f(x: Int, y: Int): Int = { f(x, y) } } // error
5+
{ def f(x: Int, y: Int): Int = f(y, x) } // error
6+
{ def f(x: Int, y: Int): Int = f(x, x) } // error
7+
{ def f(x: Int, y: Int): Int = f(1, 1) } // error
8+
{ def f(x: Int, y: Int): Int = { val a = 3; f(a, 1) } } // error
9+
10+
}

0 commit comments

Comments
 (0)