Skip to content
Browse files

SI-6780 Better handling of cycles in in-scope implicit search

Implicit searches in the body of implicit members with inferred
types were leading to cycles. Before we used to resolve that
by saying there were no implicits in scope at all; now we just
skip the current context and still include the enclosing implicits.
Care is taken not to cache results under these circumstances.

This entails reworking `Context#implicitss` so that:

  - the implicit info cache only contains implicits from the current
    level. The List[List[_]] is now contructed on demand;
  - we can detect cycles by setting `implicitsCacheRunId` to -1 during
    the computation. The outer implicits when we encounter that.
  - we avoid caching when we hit a cycle or when the owner is uninitialized.
  • Loading branch information...
1 parent b345b42 commit 0304e00168393d47e18dbb4d2c634d51bab1383a @retronym retronym committed
Showing with 45 additions and 9 deletions.
  1. +24 −9 src/compiler/scala/tools/nsc/typechecker/Contexts.scala
  2. +1 −0 test/files/neg/t712.check
  3. +20 −0 test/files/pos/t6780.scala
View
33 src/compiler/scala/tools/nsc/typechecker/Contexts.scala
@@ -773,7 +773,7 @@ trait Contexts { self: Analyzer =>
// Implicit collection
//
- private var implicitsCache: List[List[ImplicitInfo]] = null
+ private var implicitsCache: List[ImplicitInfo] = null
private var implicitsRunId = NoRunId
def resetCache() {
@@ -834,14 +834,24 @@ trait Contexts { self: Analyzer =>
def implicitss: List[List[ImplicitInfo]] = {
val imports = this.imports
val nextOuter = this.nextOuter
- if (implicitsRunId != currentRunId) {
- implicitsRunId = currentRunId
+ def withOuter(is: List[ImplicitInfo]): List[List[ImplicitInfo]] =
+ is match {
+ case Nil => nextOuter.implicitss
+ case _ => is :: nextOuter.implicitss
+ }
+
+ val CycleMarker = NoRunId - 1
+ if (implicitsRunId == CycleMarker) {
+ debuglog(s"cycle while collecting implicits at owner ${owner}, probably due to an implicit without an explicit return type. Continuing with implicits from enclosing contexts.")
+ withOuter(Nil)
+ } else if (implicitsRunId != currentRunId) {
+ implicitsRunId = CycleMarker
implicitsCache = List()
+ var canCache = true
val newImplicits: List[ImplicitInfo] =
if (owner != nextOuter.owner && owner.isClass && !owner.isPackageClass && !inSelfSuperCall) {
- if (!owner.isInitialized) return nextOuter.implicitss
- // debuglog("collect member implicits " + owner + ", implicit members = " + owner.thisType.implicitMembers)//DEBUG
- savingEnclClass(this) {
+ if (!owner.isInitialized) { canCache = false; Nil }
+ else savingEnclClass(this) {
// !!! In the body of `class C(implicit a: A) { }`, `implicitss` returns `List(List(a), List(a), List(<predef..)))`
// it handled correctly by implicit search, which considers the second `a` to be shadowed, but should be
// remedied nonetheless.
@@ -857,10 +867,15 @@ trait Contexts { self: Analyzer =>
// the corresponding package object may contain implicit members.
collectImplicits(owner.tpe.implicitMembers, owner.tpe)
} else List()
- implicitsCache = if (newImplicits.isEmpty) nextOuter.implicitss
- else newImplicits :: nextOuter.implicitss
+
+ if (canCache) {
+ implicitsRunId = currentRunId
+ implicitsCache = newImplicits
+ } else implicitsRunId = NoRunId
+
+ withOuter(newImplicits)
}
- implicitsCache
+ else withOuter(implicitsCache)
}
//
View
1 test/files/neg/t712.check
@@ -1,4 +1,5 @@
t712.scala:10: error: value self is not a member of B.this.ParentImpl
+ Note: implicit method coerce is not applicable here because it comes after the application point and it lacks an explicit result type
implicit def coerce(p : ParentImpl) = p.self;
^
one error found
View
20 test/files/pos/t6780.scala
@@ -0,0 +1,20 @@
+object O {
+ implicit def i: Int = 0
+}
+
+import O._
+
+trait Foo {
+ implicit val v1: Any
+ implicit def d1: Any
+ val v2: Any
+ implicit val v3: Any
+}
+
+trait Bar1 extends Foo {
+ implicit val v1 = {implicitly[Int]; ()} // failed due to cycle in Context#implicits being broken with Nil.
+ def d1 = {implicitly[Int]; ()} // okay
+ implicit val v2 = {implicitly[Int]; ()} // okay
+ implicit val v3: Any = {implicitly[Int]; ()} // okay
+
+}

0 comments on commit 0304e00

Please sign in to comment.
Something went wrong with that request. Please try again.