diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 049d6d06a466..d4d1415a6c8c 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -14,7 +14,7 @@ import Uniques.* import ast.Trees.* import Flags.ParamAccessor import ast.untpd -import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, WrappedSourceFile} +import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, WrappedSourceFile, HashMap} import typer.{Implicits, ImportInfo, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables} import inlines.Inliner import Nullables.* @@ -125,6 +125,18 @@ object Contexts { * of all class fields of type context; allow them only in allowlisted * classes (which should be short-lived). */ + + /** Identity-based key for the per-context `asSeenFrom` cache. */ + private[dotc] final class AsfKey(val tp: Type, val pre: Type, val cls: Symbol): + override def hashCode: Int = + ((System.identityHashCode(tp) * 31 + + System.identityHashCode(pre)) * 31 + + System.identityHashCode(cls)) + override def equals(other: Any): Boolean = other match + case that: AsfKey => + (tp eq that.tp) && (pre eq that.pre) && (cls eq that.cls) + case _ => false + abstract class Context(val base: ContextBase) { thiscontext => protected given Context = this @@ -275,6 +287,13 @@ object Contexts { None ) + /** Cache for `TypeOps.asSeenFrom` results computed in this context. */ + private var asSeenFromCacheMap: HashMap[AsfKey, Type] | Null = null + private[dotc] def asSeenFromCache: HashMap[AsfKey, Type] = + if asSeenFromCacheMap == null then + asSeenFromCacheMap = HashMap() + asSeenFromCacheMap.nn + private var related: SimpleIdentityMap[Phase | SourceFile, Context] | Null = null private def lookup(key: Phase | SourceFile): Context | Null = @@ -551,6 +570,7 @@ object Contexts { protected def resetCaches(): Unit = implicitsCache = null related = null + asSeenFromCacheMap = null /** Reuse this context as a fresh context nested inside `outer` * But keep the typerstate, this one has to be set explicitly if needed. diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 82f04a61b0f7..8b7f6aba3729 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -53,7 +53,16 @@ object TypeOps: case _ => } - new AsSeenFromMap(pre, cls).apply(tp) + if tp.isProvisional || pre.isProvisional then + new AsSeenFromMap(pre, cls).apply(tp) + else + var hit = true + val res = ctx.asSeenFromCache.getOrElseUpdate(AsfKey(tp, pre, cls), { + hit = false + new AsSeenFromMap(pre, cls).apply(tp) + }) + Stats.record(if hit then "asSeenFrom cache hit" else "asSeenFrom cache miss") + res } /** The TypeMap handling the asSeenFrom */