Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,26 +22,46 @@ private sealed class ProjectIndex(
MultiDictionary<DocumentId, DeclaredSymbolInfo> delegates,
MultiDictionary<string, (DocumentId, DeclaredSymbolInfo)> namedTypes)
{
private static readonly ConditionalWeakTable<ProjectState, AsyncLazy<ProjectIndex>> s_projectToIndex = new();
/// <summary>
/// We cache the project instance per <see cref="ProjectState"/>. 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, <see cref="ProjectState"/> doesn't change even when certain things change that will create a
/// substantively different <see cref="Project"/>. For example, if the <see
/// cref="SourceGeneratorExecutionVersion"/> for the project changes, we'll still have the same project state.
/// As such, we store the <see cref="Checksum"/> of the project as well, ensuring that if anything in it or its
/// dependencies changes, we recompute the index.
/// </summary>
private static readonly ConditionalWeakTable<ProjectState, StrongBox<(Checksum checksum, AsyncLazy<ProjectIndex> lazyProjectIndex)>> s_projectToIndex = new();

public readonly MultiDictionary<DocumentId, DeclaredSymbolInfo> ClassesAndRecordsThatMayDeriveFromSystemObject = classesAndRecordsThatMayDeriveFromSystemObject;
public readonly MultiDictionary<DocumentId, DeclaredSymbolInfo> ValueTypes = valueTypes;
public readonly MultiDictionary<DocumentId, DeclaredSymbolInfo> Enums = enums;
public readonly MultiDictionary<DocumentId, DeclaredSymbolInfo> Delegates = delegates;
public readonly MultiDictionary<string, (DocumentId, DeclaredSymbolInfo)> NamedTypes = namedTypes;

public static Task<ProjectIndex> GetIndexAsync(
public static async Task<ProjectIndex> 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<ProjectIndex> CreateIndexAsync(Project project, CancellationToken cancellationToken)
Expand Down
Loading