diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs index 083c3d0937a87..8d6fec248ec0e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Storage; using Roslyn.Utilities; @@ -21,7 +22,16 @@ private sealed class ProjectIndex( MultiDictionary delegates, MultiDictionary namedTypes) { - private static readonly ConditionalWeakTable> s_projectToIndex = new(); + /// + /// We cache the project instance per . This allows us to reuse it over a wide set of + /// changes (for example, changing completely unrelated projects that a particular project doesn't depend on). + /// However, doesn't change even when certain things change that will create a + /// substantively different . For example, if the for the project changes, we'll still have the same project state. + /// As such, we store the of the project as well, ensuring that if anything in it or its + /// dependencies changes, we recompute the index. + /// + private static readonly ConditionalWeakTable lazyProjectIndex)>> s_projectToIndex = new(); public readonly MultiDictionary ClassesAndRecordsThatMayDeriveFromSystemObject = classesAndRecordsThatMayDeriveFromSystemObject; public readonly MultiDictionary ValueTypes = valueTypes; @@ -29,18 +39,29 @@ private sealed class ProjectIndex( public readonly MultiDictionary Delegates = delegates; public readonly MultiDictionary NamedTypes = namedTypes; - public static Task GetIndexAsync( + public static async Task GetIndexAsync( Project project, CancellationToken cancellationToken) { - if (!s_projectToIndex.TryGetValue(project.State, out var lazyIndex)) + // Use the checksum of the project. That way if its state *or* SG info changes, we compute a new index with + // accurate information in it. + var checksum = await project.GetDiagnosticChecksumAsync(cancellationToken).ConfigureAwait(false); + if (!s_projectToIndex.TryGetValue(project.State, out var tuple) || + tuple.Value.checksum != checksum) { - lazyIndex = s_projectToIndex.GetValue( - project.State, p => AsyncLazy.Create( - static (project, c) => CreateIndexAsync(project, c), - project)); + tuple = new((checksum, AsyncLazy.Create(CreateIndexAsync, project))); + +#if NET + s_projectToIndex.AddOrUpdate(project.State, tuple); +#else + // Best effort try to update the map with the new data. + s_projectToIndex.Remove(project.State); + // Note: intentionally ignore the return value here. We want to use the value we've computed even if + // another thread beats us to adding things here. + _ = s_projectToIndex.GetValue(project.State, _ => tuple); +#endif } - return lazyIndex.GetValueAsync(cancellationToken); + return await tuple.Value.lazyProjectIndex.GetValueAsync(cancellationToken).ConfigureAwait(false); } private static async Task CreateIndexAsync(Project project, CancellationToken cancellationToken)