@@ -259,7 +259,7 @@ object Implicits {
259
259
prefix ++ first ++ rest
260
260
}
261
261
}
262
-
262
+
263
263
def explanation (implicit ctx : Context ): String = {
264
264
val headMsg = em " ${err.refStr(ref)} does not $qualify"
265
265
val trailMsg = trail.map(mc => i " $separator ${mc.message}" ).mkString
@@ -302,9 +302,10 @@ trait ImplicitRunInfo { self: RunInfo =>
302
302
* a type variable, we need the current context, the current
303
303
* runinfo context does not do.
304
304
*/
305
- def implicitScope (tp : Type , liftingCtx : Context ): OfTypeImplicits = {
305
+ def implicitScope (rootTp : Type , liftingCtx : Context ): OfTypeImplicits = {
306
306
307
307
val seen : mutable.Set [Type ] = mutable.Set ()
308
+ val incomplete : mutable.Set [Type ] = mutable.Set ()
308
309
309
310
/** Replace every typeref that does not refer to a class by a conjunction of class types
310
311
* that has the same implicit scope as the original typeref. The motivation for applying
@@ -338,16 +339,23 @@ trait ImplicitRunInfo { self: RunInfo =>
338
339
}
339
340
}
340
341
341
- def iscopeRefs (tp : Type ): TermRefSet =
342
- if (seen contains tp) EmptyTermRefSet
343
- else {
344
- seen += tp
345
- iscope(tp).companionRefs
346
- }
347
-
348
342
// todo: compute implicits directly, without going via companionRefs?
349
343
def collectCompanions (tp : Type ): TermRefSet = track(" computeImplicitScope" ) {
350
344
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
+
351
359
val comps = new TermRefSet
352
360
tp match {
353
361
case tp : NamedType =>
@@ -385,7 +393,8 @@ trait ImplicitRunInfo { self: RunInfo =>
385
393
* @param isLifted Type `tp` is the result of a `liftToClasses` application
386
394
*/
387
395
def iscope (tp : Type , isLifted : Boolean = false ): OfTypeImplicits = {
388
- def computeIScope (cacheResult : Boolean ) = {
396
+ val canCache = Config .cacheImplicitScopes && tp.hash != NotCached
397
+ def computeIScope () = {
389
398
val savedEphemeral = ctx.typerState.ephemeral
390
399
ctx.typerState.ephemeral = false
391
400
try {
@@ -396,33 +405,23 @@ trait ImplicitRunInfo { self: RunInfo =>
396
405
else
397
406
collectCompanions(tp)
398
407
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
401
416
result
402
417
}
403
418
finally ctx.typerState.ephemeral |= savedEphemeral
404
419
}
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()
423
422
}
424
423
425
- iscope(tp )
424
+ iscope(rootTp )
426
425
}
427
426
428
427
/** A map that counts the number of times an implicit ref was picked */
0 commit comments