Skip to content

Commit b21f954

Browse files
committed
Avoid recomputation of companionRefs
When tracing i1639.scala it became apparent that we compute a lot of companion refs. This commit avoids this by better book-keeping what is valid and what is not and more aggressive caching.
1 parent e6e47f1 commit b21f954

File tree

1 file changed

+30
-31
lines changed

1 file changed

+30
-31
lines changed

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ object Implicits {
259259
prefix ++ first ++ rest
260260
}
261261
}
262-
262+
263263
def explanation(implicit ctx: Context): String = {
264264
val headMsg = em"${err.refStr(ref)} does not $qualify"
265265
val trailMsg = trail.map(mc => i"$separator ${mc.message}").mkString
@@ -302,9 +302,10 @@ trait ImplicitRunInfo { self: RunInfo =>
302302
* a type variable, we need the current context, the current
303303
* runinfo context does not do.
304304
*/
305-
def implicitScope(tp: Type, liftingCtx: Context): OfTypeImplicits = {
305+
def implicitScope(rootTp: Type, liftingCtx: Context): OfTypeImplicits = {
306306

307307
val seen: mutable.Set[Type] = mutable.Set()
308+
val incomplete: mutable.Set[Type] = mutable.Set()
308309

309310
/** Replace every typeref that does not refer to a class by a conjunction of class types
310311
* that has the same implicit scope as the original typeref. The motivation for applying
@@ -338,16 +339,23 @@ trait ImplicitRunInfo { self: RunInfo =>
338339
}
339340
}
340341

341-
def iscopeRefs(tp: Type): TermRefSet =
342-
if (seen contains tp) EmptyTermRefSet
343-
else {
344-
seen += tp
345-
iscope(tp).companionRefs
346-
}
347-
348342
// todo: compute implicits directly, without going via companionRefs?
349343
def collectCompanions(tp: Type): TermRefSet = track("computeImplicitScope") {
350344
ctx.traceIndented(i"collectCompanions($tp)", implicits) {
345+
346+
def iscopeRefs(t: Type): TermRefSet = implicitScopeCache.get(t) match {
347+
case Some(is) =>
348+
is.companionRefs
349+
case None =>
350+
if (seen contains t) {
351+
incomplete += tp // all references to rootTo will be accounted for in `seen` so we return `EmptySet`.
352+
EmptyTermRefSet // on the other hand, the refs of `tp` are now not accurate, so `tp` is marked incomplete.
353+
} else {
354+
seen += t
355+
iscope(t).companionRefs
356+
}
357+
}
358+
351359
val comps = new TermRefSet
352360
tp match {
353361
case tp: NamedType =>
@@ -385,7 +393,8 @@ trait ImplicitRunInfo { self: RunInfo =>
385393
* @param isLifted Type `tp` is the result of a `liftToClasses` application
386394
*/
387395
def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = {
388-
def computeIScope(cacheResult: Boolean) = {
396+
val canCache = Config.cacheImplicitScopes && tp.hash != NotCached
397+
def computeIScope() = {
389398
val savedEphemeral = ctx.typerState.ephemeral
390399
ctx.typerState.ephemeral = false
391400
try {
@@ -396,33 +405,23 @@ trait ImplicitRunInfo { self: RunInfo =>
396405
else
397406
collectCompanions(tp)
398407
val result = new OfTypeImplicits(tp, refs)(ctx)
399-
if (ctx.typerState.ephemeral) record("ephemeral cache miss: implicitScope")
400-
else if (cacheResult) implicitScopeCache(tp) = result
408+
if (ctx.typerState.ephemeral)
409+
record("ephemeral cache miss: implicitScope")
410+
else if (canCache &&
411+
((tp eq rootTp) || // first type traversed is always cached
412+
!incomplete.contains(tp) && // other types are cached if they are not incomplete
413+
result.companionRefs.forall( // and all their companion refs are cached
414+
implicitScopeCache.contains)))
415+
implicitScopeCache(tp) = result
401416
result
402417
}
403418
finally ctx.typerState.ephemeral |= savedEphemeral
404419
}
405-
406-
if (tp.hash == NotCached || !Config.cacheImplicitScopes)
407-
computeIScope(cacheResult = false)
408-
else implicitScopeCache get tp match {
409-
case Some(is) => is
410-
case None =>
411-
// Implicit scopes are tricky to cache because of loops. For example
412-
// in `tests/pos/implicit-scope-loop.scala`, the scope of B contains
413-
// the scope of A which contains the scope of B. We break the loop
414-
// by returning EmptyTermRefSet in `collectCompanions` for types
415-
// that we have already seen, but this means that we cannot cache
416-
// the computed scope of A, it is incomplete.
417-
// Keeping track of exactly where these loops happen would require a
418-
// lot of book-keeping, instead we choose to be conservative and only
419-
// cache scopes before any type has been seen. This is unfortunate
420-
// because loops are very common for types in scala.collection.
421-
computeIScope(cacheResult = seen.isEmpty)
422-
}
420+
if (canCache) implicitScopeCache.getOrElse(tp, computeIScope())
421+
else computeIScope()
423422
}
424423

425-
iscope(tp)
424+
iscope(rootTp)
426425
}
427426

428427
/** A map that counts the number of times an implicit ref was picked */

0 commit comments

Comments
 (0)