From ddf73dd72f1698d85e95da0c8a7586bb68134321 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 8 Jan 2020 14:00:28 +0100 Subject: [PATCH 1/3] Fix #7821: Warn on simple infinite tail rec loops --- .../dotty/tools/dotc/transform/TailRec.scala | 20 ++++++++++++++++ .../fatal-warnings/i7821.scala | 24 +++++++++++++++++++ .../fatal-warnings/i7821b.scala | 10 ++++++++ 3 files changed, 54 insertions(+) create mode 100644 tests/neg-custom-args/fatal-warnings/i7821.scala create mode 100644 tests/neg-custom-args/fatal-warnings/i7821b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 15fb388b4d70..f3c45c03acb7 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,25 @@ class TailRec extends MiniPhase { ).transform(rhsSemiTransformed) } + // Is a simple this a simple recursive tail call, possibly with swapped or modified pure arguments + def isInfiniteRecCall(tree: Tree): Boolean = { + def statOk(stat: Tree): Boolean = stat match { + case stat: ValDef if stat.name.is(TailTempName) || !stat.symbol.is(Mutable) => statOk(stat.rhs) + case Assign(lhs: Ident, rhs: Ident) if lhs.symbol.name.is(TailLocalName) => statOk(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(statOk) && 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..401639865294 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i7821b.scala @@ -0,0 +1,10 @@ +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 + +} From 2aca3be28617077fe88c161ae01a4b0f855baec5 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 9 Jan 2020 09:05:08 +0100 Subject: [PATCH 2/3] Support more simple recursions --- compiler/src/dotty/tools/dotc/transform/TailRec.scala | 2 +- tests/neg-custom-args/fatal-warnings/i7821b.scala | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index f3c45c03acb7..ac5d4f272daf 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -179,7 +179,7 @@ class TailRec extends MiniPhase { def isInfiniteRecCall(tree: Tree): Boolean = { def statOk(stat: Tree): Boolean = stat match { case stat: ValDef if stat.name.is(TailTempName) || !stat.symbol.is(Mutable) => statOk(stat.rhs) - case Assign(lhs: Ident, rhs: Ident) if lhs.symbol.name.is(TailLocalName) => statOk(rhs) + case Assign(lhs: Ident, rhs) if lhs.symbol.name.is(TailLocalName) => statOk(rhs) case stat: Ident if stat.symbol.name.is(TailLocalName) => true case _ => tpd.isPureExpr(stat) } diff --git a/tests/neg-custom-args/fatal-warnings/i7821b.scala b/tests/neg-custom-args/fatal-warnings/i7821b.scala index 401639865294..9e38b33b0cb3 100644 --- a/tests/neg-custom-args/fatal-warnings/i7821b.scala +++ b/tests/neg-custom-args/fatal-warnings/i7821b.scala @@ -6,5 +6,6 @@ object Test { { 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 } From 07fa26002b1db1d516bbc4a6ff59491aff90e0a8 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 9 Jan 2020 09:13:57 +0100 Subject: [PATCH 3/3] Improve documentation --- .../src/dotty/tools/dotc/transform/TailRec.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index ac5d4f272daf..8965da985df2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -175,18 +175,23 @@ class TailRec extends MiniPhase { ).transform(rhsSemiTransformed) } - // Is a simple this a simple recursive tail call, possibly with swapped or modified pure arguments + /** 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 statOk(stat: Tree): Boolean = stat match { - case stat: ValDef if stat.name.is(TailTempName) || !stat.symbol.is(Mutable) => statOk(stat.rhs) - case Assign(lhs: Ident, rhs) if lhs.symbol.name.is(TailLocalName) => statOk(rhs) + 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(statOk) && isInfiniteRecCall(expr) + case Block(stats, expr) => stats.forall(tailArgOrPureExpr) && isInfiniteRecCall(expr) case _ => false } }