diff options
-rw-r--r-- | src/dotty/tools/dotc/typer/Implicits.scala | 13 | ||||
-rw-r--r-- | tests/pos/implicit-scope-loop.scala | 17 |
2 files changed, 29 insertions, 1 deletions
diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 38ac709be..b3a1010cb 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -363,7 +363,18 @@ trait ImplicitRunInfo { self: RunInfo => computeIScope(cacheResult = false) else implicitScopeCache get tp match { case Some(is) => is - case None => computeIScope(cacheResult = seen.isEmpty) + case None => + // Implicit scopes are tricky to cache because of loops. For example + // in `tests/pos/implicit-scope-loop.scala`, the scope of B contains + // the scope of A which contains the scope of B. We break the loop + // by returning EmptyTermRefSet in `collectCompanions` for types + // that we have already seen, but this means that we cannot cache + // the computed scope of A, it is incomplete. + // Keeping track of exactly where these loops happen would require a + // lot of book-keeping, instead we choose to be conservative and only + // cache scopes before any type has been seen. This is unfortunate + // because loops are very common for types in scala.collection. + computeIScope(cacheResult = seen.isEmpty) } } diff --git a/tests/pos/implicit-scope-loop.scala b/tests/pos/implicit-scope-loop.scala new file mode 100644 index 000000000..162f1a52c --- /dev/null +++ b/tests/pos/implicit-scope-loop.scala @@ -0,0 +1,17 @@ +trait Dummy[T] + + +trait A[T] extends B +trait B extends Dummy[A[Int]] +object B { + implicit def theB: B = new B {} + implicit def theA: A[Int] = new A[Int] {} +} + +object Test { + def getB(implicit b: B) = b + def getA[T](implicit a: A[T]) = a + + getB + getA +}
\ No newline at end of file |