Skip to content

Commit 09de63c

Browse files
Merge pull request #7924 from dotty-staging/fix-#7821
Fix #7821: Warn on simple infinite tailrec loops
2 parents a360b66 + 07fa260 commit 09de63c

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

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

Lines changed: 25 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,30 @@ class TailRec extends MiniPhase {
174175
).transform(rhsSemiTransformed)
175176
}
176177

178+
/** Is the RHS a direct recursive tailcall, possibly with swapped arguments or modified pure arguments.
179+
* ```
180+
* def f(<params>): T = f(<args>)
181+
* ```
182+
* where `<args>` are pure arguments or references to parameters in `<params>`.
183+
*/
184+
def isInfiniteRecCall(tree: Tree): Boolean = {
185+
def tailArgOrPureExpr(stat: Tree): Boolean = stat match {
186+
case stat: ValDef if stat.name.is(TailTempName) || !stat.symbol.is(Mutable) => tailArgOrPureExpr(stat.rhs)
187+
case Assign(lhs: Ident, rhs) if lhs.symbol.name.is(TailLocalName) => tailArgOrPureExpr(rhs)
188+
case stat: Ident if stat.symbol.name.is(TailLocalName) => true
189+
case _ => tpd.isPureExpr(stat)
190+
}
191+
tree match {
192+
case Typed(expr, _) => isInfiniteRecCall(expr)
193+
case Return(Literal(Constant(())), label) => label.symbol == transformer.continueLabel
194+
case Block(stats, expr) => stats.forall(tailArgOrPureExpr) && isInfiniteRecCall(expr)
195+
case _ => false
196+
}
197+
}
198+
199+
if isInfiniteRecCall(rhsFullyTransformed) then
200+
ctx.warning("Infinite recursive call", tree.sourcePos)
201+
177202
cpy.DefDef(tree)(rhs =
178203
Block(
179204
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
{ def f(x: Int): Int = f(1) } // error
10+
11+
}

0 commit comments

Comments
 (0)