diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index a051cba5ba05..aeb63d9ff291 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -64,6 +64,7 @@ class Compiler { new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes new CookComments, // Cook the comments: expand variables, doc, etc. new CheckStatic, // Check restrictions that apply to @static members + new CheckLoopingImplicits, // Check that implicit defs do not call themselves in an infinite loop new BetaReduce, // Reduce closure applications new InlineVals, // Check right hand-sides of an `inline val`s new ExpandSAMs) :: // Expand single abstract method closures to anonymous classes diff --git a/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala new file mode 100644 index 000000000000..5f8a8ee054f6 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala @@ -0,0 +1,81 @@ +package dotty.tools.dotc +package transform + +import core.* +import MegaPhase.MiniPhase +import Contexts.*, Types.*, Symbols.*, SymDenotations.*, Flags.* +import ast.* +import Trees.* +import Decorators.* + +import annotation.threadUnsafe + +object CheckLoopingImplicits: + val name: String = "checkLoopingImplicits" + +/** Checks that implicit defs do not call themselves in an infinite loop */ +class CheckLoopingImplicits extends MiniPhase: + thisPhase => + import tpd._ + + override def phaseName: String = CheckLoopingImplicits.name + + override def transformDefDef(mdef: DefDef)(using Context): DefDef = + val sym = mdef.symbol + + def checkNotSelfRef(t: RefTree) = + if t.symbol eq sym then + report.warning( + em"""Infinite loop in function body + |${mdef.rhs}""", + mdef.rhs.srcPos + ) + + def checkNotLooping(t: Tree): Unit = t match + case t: Ident => + checkNotSelfRef(t) + case t @ Select(qual, _) => + checkNotSelfRef(t) + checkNotLooping(qual) + case Apply(fn, args) => + checkNotLooping(fn) + fn.tpe.widen match + case mt: MethodType => + args.lazyZip(mt.paramInfos).foreach { (arg, pinfo) => + if !pinfo.isInstanceOf[ExprType] then checkNotLooping(arg) + } + case _ => + case TypeApply(fn, _) => + checkNotLooping(fn) + case Block(stats, expr) => + stats.foreach(checkNotLooping) + checkNotLooping(expr) + case Typed(expr, _) => + checkNotLooping(expr) + case Assign(lhs, rhs) => + checkNotLooping(lhs) + checkNotLooping(rhs) + case If(cond, _, _) => + checkNotLooping(cond) + case Match(selector, _) => + checkNotLooping(selector) + case Labeled(_, expr) => + checkNotLooping(expr) + case Return(expr, _) => + checkNotLooping(expr) + case WhileDo(cond, _) => + checkNotLooping(cond) + case Try(block, _, finalizer) => + checkNotLooping(block) + checkNotLooping(finalizer) + case SeqLiteral(elems, _) => + elems.foreach(checkNotLooping) + case t: ValDef => + if !t.symbol.is(Lazy) then checkNotLooping(t.rhs) + case _ => + + if sym.isOneOf(GivenOrImplicit) then + checkNotLooping(mdef.rhs) + mdef + end transformDefDef +end CheckLoopingImplicits \ No newline at end of file diff --git a/tests/neg-custom-args/fatal-warnings/i13542.scala b/tests/neg-custom-args/fatal-warnings/i13542.scala new file mode 100644 index 000000000000..1c5fa4958c75 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i13542.scala @@ -0,0 +1,50 @@ +import scala.language.implicitConversions + +case class Foo(i: Int) extends AnyVal: + def toFoo = this + +case class Bar(i: Int) extends AnyVal + +class BarOps(bar: Bar): + def toFoo = Foo(bar.i) + +implicit def augmentBar(bar: Bar): BarOps = BarOps(bar) + +def lazyIdentity[T](x: => T): T = x +def repIdentity[T](x: T*): T = x(0) + +val x1 = + implicit def barToFoo(bar: Bar): Foo = bar.toFoo // error: infinite loop in function body + val foo: Foo = Bar(1) + +val x2 = + implicit def barToFoo2(bar: Bar): Foo = + identity(bar.toFoo) // error + val foo: Foo = Bar(1) + +val x3 = + implicit def barToFoo3(bar: Bar): Foo = + lazyIdentity(bar.toFoo) // OK + val foo: Foo = Bar(1) + +val x4 = + implicit def barToFoo4(bar: Bar): Foo = + repIdentity(bar.toFoo) // error + val foo: Foo = Bar(1) + +val x5 = + implicit def barToFoo4(bar: Bar): Foo = + val y = bar.toFoo // error + y + val foo: Foo = Bar(1) + +val x6 = + implicit def barToFoo4(bar: Bar): Foo = + lazy val y = bar.toFoo // OK + if false then y else ??? + val foo: Foo = Bar(1) + + + + +