From 0304e00168393d47e18dbb4d2c634d51bab1383a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 11 Dec 2013 15:56:37 +0100 Subject: 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. --- .../scala/tools/nsc/typechecker/Contexts.scala | 33 ++++++++++++++++------ 1 file changed, 24 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 2be6d92ed0..b7a942fc0d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/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( // 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) } // -- cgit v1.2.3