diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 15fb388b4d70..8965da985df2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -5,6 +5,7 @@ import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{TreeTypeMap, tpd} import dotty.tools.dotc.config.Printers.tailrec import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.NameKinds.{TailLabelName, TailLocalName, TailTempName} @@ -174,6 +175,30 @@ class TailRec extends MiniPhase { ).transform(rhsSemiTransformed) } + /** Is the RHS a direct recursive tailcall, possibly with swapped arguments or modified pure arguments. + * ``` + * def f(): T = f() + * ``` + * where `` are pure arguments or references to parameters in ``. + */ + def isInfiniteRecCall(tree: Tree): Boolean = { + def tailArgOrPureExpr(stat: Tree): Boolean = stat match { + case stat: ValDef if stat.name.is(TailTempName) || !stat.symbol.is(Mutable) => tailArgOrPureExpr(stat.rhs) + case Assign(lhs: Ident, rhs) if lhs.symbol.name.is(TailLocalName) => tailArgOrPureExpr(rhs) + case stat: Ident if stat.symbol.name.is(TailLocalName) => true + case _ => tpd.isPureExpr(stat) + } + tree match { + case Typed(expr, _) => isInfiniteRecCall(expr) + case Return(Literal(Constant(())), label) => label.symbol == transformer.continueLabel + case Block(stats, expr) => stats.forall(tailArgOrPureExpr) && isInfiniteRecCall(expr) + case _ => false + } + } + + if isInfiniteRecCall(rhsFullyTransformed) then + ctx.warning("Infinite recursive call", tree.sourcePos) + cpy.DefDef(tree)(rhs = Block( initialVarDefs, diff --git a/tests/neg-custom-args/fatal-warnings/i7821.scala b/tests/neg-custom-args/fatal-warnings/i7821.scala new file mode 100644 index 000000000000..4376a7f2e6fc --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i7821.scala @@ -0,0 +1,24 @@ +object XObject { + opaque type X = Int + + def anX: X = 5 + + given ops: Object { + def (x: X) + (y: X): X = x + y + } +} + +object MyXObject { + opaque type MyX = XObject.X + + def anX: MyX = XObject.anX + + given ops: Object { + def (x: MyX) + (y: MyX): MyX = x + y // error: warring: Infinite recursive call + } +} + +object Main extends App { + println(XObject.anX + XObject.anX) // prints 10 + println(MyXObject.anX + MyXObject.anX) // infinite loop +} diff --git a/tests/neg-custom-args/fatal-warnings/i7821b.scala b/tests/neg-custom-args/fatal-warnings/i7821b.scala new file mode 100644 index 000000000000..9e38b33b0cb3 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i7821b.scala @@ -0,0 +1,11 @@ +object Test { + + { def f(x: Int, y: Int): Int = f(x, y) } // error + { def f(x: Int, y: Int): Int = { f(x, y) } } // error + { def f(x: Int, y: Int): Int = f(y, x) } // error + { def f(x: Int, y: Int): Int = f(x, x) } // error + { def f(x: Int, y: Int): Int = f(1, 1) } // error + { def f(x: Int, y: Int): Int = { val a = 3; f(a, 1) } } // error + { def f(x: Int): Int = f(1) } // error + +}