Skip to content

Commit 156e1c4

Browse files
authored
Merge pull request #13589 from dotty-staging/fix-13542
Check that right hand sides of implicit defs don't loop (directly)
2 parents 3f978b3 + 9a50c1f commit 156e1c4

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class Compiler {
6464
new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes
6565
new CookComments, // Cook the comments: expand variables, doc, etc.
6666
new CheckStatic, // Check restrictions that apply to @static members
67+
new CheckLoopingImplicits, // Check that implicit defs do not call themselves in an infinite loop
6768
new BetaReduce, // Reduce closure applications
6869
new InlineVals, // Check right hand-sides of an `inline val`s
6970
new ExpandSAMs) :: // Expand single abstract method closures to anonymous classes
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core.*
5+
import MegaPhase.MiniPhase
6+
import Contexts.*, Types.*, Symbols.*, SymDenotations.*, Flags.*
7+
import ast.*
8+
import Trees.*
9+
import Decorators.*
10+
11+
import annotation.threadUnsafe
12+
13+
object CheckLoopingImplicits:
14+
val name: String = "checkLoopingImplicits"
15+
16+
/** Checks that implicit defs do not call themselves in an infinite loop */
17+
class CheckLoopingImplicits extends MiniPhase:
18+
thisPhase =>
19+
import tpd._
20+
21+
override def phaseName: String = CheckLoopingImplicits.name
22+
23+
override def transformDefDef(mdef: DefDef)(using Context): DefDef =
24+
val sym = mdef.symbol
25+
26+
def checkNotSelfRef(t: RefTree) =
27+
if t.symbol eq sym then
28+
report.warning(
29+
em"""Infinite loop in function body
30+
|${mdef.rhs}""",
31+
mdef.rhs.srcPos
32+
)
33+
34+
def checkNotLooping(t: Tree): Unit = t match
35+
case t: Ident =>
36+
checkNotSelfRef(t)
37+
case t @ Select(qual, _) =>
38+
checkNotSelfRef(t)
39+
checkNotLooping(qual)
40+
case Apply(fn, args) =>
41+
checkNotLooping(fn)
42+
fn.tpe.widen match
43+
case mt: MethodType =>
44+
args.lazyZip(mt.paramInfos).foreach { (arg, pinfo) =>
45+
if !pinfo.isInstanceOf[ExprType] then checkNotLooping(arg)
46+
}
47+
case _ =>
48+
case TypeApply(fn, _) =>
49+
checkNotLooping(fn)
50+
case Block(stats, expr) =>
51+
stats.foreach(checkNotLooping)
52+
checkNotLooping(expr)
53+
case Typed(expr, _) =>
54+
checkNotLooping(expr)
55+
case Assign(lhs, rhs) =>
56+
checkNotLooping(lhs)
57+
checkNotLooping(rhs)
58+
case If(cond, _, _) =>
59+
checkNotLooping(cond)
60+
case Match(selector, _) =>
61+
checkNotLooping(selector)
62+
case Labeled(_, expr) =>
63+
checkNotLooping(expr)
64+
case Return(expr, _) =>
65+
checkNotLooping(expr)
66+
case WhileDo(cond, _) =>
67+
checkNotLooping(cond)
68+
case Try(block, _, finalizer) =>
69+
checkNotLooping(block)
70+
checkNotLooping(finalizer)
71+
case SeqLiteral(elems, _) =>
72+
elems.foreach(checkNotLooping)
73+
case t: ValDef =>
74+
if !t.symbol.is(Lazy) then checkNotLooping(t.rhs)
75+
case _ =>
76+
77+
if sym.isOneOf(GivenOrImplicit) then
78+
checkNotLooping(mdef.rhs)
79+
mdef
80+
end transformDefDef
81+
end CheckLoopingImplicits
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import scala.language.implicitConversions
2+
3+
case class Foo(i: Int) extends AnyVal:
4+
def toFoo = this
5+
6+
case class Bar(i: Int) extends AnyVal
7+
8+
class BarOps(bar: Bar):
9+
def toFoo = Foo(bar.i)
10+
11+
implicit def augmentBar(bar: Bar): BarOps = BarOps(bar)
12+
13+
def lazyIdentity[T](x: => T): T = x
14+
def repIdentity[T](x: T*): T = x(0)
15+
16+
val x1 =
17+
implicit def barToFoo(bar: Bar): Foo = bar.toFoo // error: infinite loop in function body
18+
val foo: Foo = Bar(1)
19+
20+
val x2 =
21+
implicit def barToFoo2(bar: Bar): Foo =
22+
identity(bar.toFoo) // error
23+
val foo: Foo = Bar(1)
24+
25+
val x3 =
26+
implicit def barToFoo3(bar: Bar): Foo =
27+
lazyIdentity(bar.toFoo) // OK
28+
val foo: Foo = Bar(1)
29+
30+
val x4 =
31+
implicit def barToFoo4(bar: Bar): Foo =
32+
repIdentity(bar.toFoo) // error
33+
val foo: Foo = Bar(1)
34+
35+
val x5 =
36+
implicit def barToFoo4(bar: Bar): Foo =
37+
val y = bar.toFoo // error
38+
y
39+
val foo: Foo = Bar(1)
40+
41+
val x6 =
42+
implicit def barToFoo4(bar: Bar): Foo =
43+
lazy val y = bar.toFoo // OK
44+
if false then y else ???
45+
val foo: Foo = Bar(1)
46+
47+
48+
49+
50+

0 commit comments

Comments
 (0)