Skip to content

Commit 329bb90

Browse files
Remove 'state set' concept for diagnostic analysis subsystem. (#77121)
2 parents 9f3c6eb + 38b6b74 commit 329bb90

11 files changed

+240
-308
lines changed

src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ internal partial class DiagnosticAnalyzerService
1919
{
2020
/// <summary>
2121
/// Cached data from a <see cref="Project"/> to the last <see cref="CompilationWithAnalyzersPair"/> instance created
22-
/// for it. Note: the CompilationWithAnalyzersPair instance is dependent on the set of <see cref="StateSet"/>s
23-
/// passed along with the project. As such, we might not be able to use a prior cached value if the set of state
24-
/// sets changes. In that case, a new instance will be created and will be cached for the next caller.
22+
/// for it. Note: the CompilationWithAnalyzersPair instance is dependent on the set of <see
23+
/// cref="DiagnosticAnalyzer"/>s passed along with the project. As such, we might not be able to use a prior cached
24+
/// value if the set of analyzers changes. In that case, a new instance will be created and will be cached for the
25+
/// next caller.
2526
/// </summary>
26-
private static readonly ConditionalWeakTable<Project, StrongBox<(ImmutableArray<StateSet> stateSets, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new();
27+
private static readonly ConditionalWeakTable<Project, StrongBox<(ImmutableArray<DiagnosticAnalyzer> analyzers, CompilationWithAnalyzersPair? compilationWithAnalyzersPair)>> s_projectToCompilationWithAnalyzers = new();
2728

2829
private static async Task<CompilationWithAnalyzersPair?> GetOrCreateCompilationWithAnalyzersAsync(
2930
Project project,
30-
ImmutableArray<StateSet> stateSets,
31+
ImmutableArray<DiagnosticAnalyzer> analyzers,
32+
HostAnalyzerInfo hostAnalyzerInfo,
3133
bool crashOnAnalyzerException,
3234
CancellationToken cancellationToken)
3335
{
@@ -37,10 +39,10 @@ internal partial class DiagnosticAnalyzerService
3739
// Make sure the cached pair was computed with at least the same state sets we're asking about. if not,
3840
// recompute and cache with the new state sets.
3941
if (!s_projectToCompilationWithAnalyzers.TryGetValue(project, out var tupleBox) ||
40-
!stateSets.IsSubsetOf(tupleBox.Value.stateSets))
42+
!analyzers.IsSubsetOf(tupleBox.Value.analyzers))
4143
{
4244
var compilationWithAnalyzersPair = await CreateCompilationWithAnalyzersAsync().ConfigureAwait(false);
43-
tupleBox = new((stateSets, compilationWithAnalyzersPair));
45+
tupleBox = new((analyzers, compilationWithAnalyzersPair));
4446

4547
// Make a best effort attempt to store the latest computed value against these state sets. If this
4648
// fails (because another thread interleaves with this), that's ok. We still return the pair we
@@ -59,8 +61,8 @@ internal partial class DiagnosticAnalyzerService
5961
// </summary>
6062
async Task<CompilationWithAnalyzersPair?> CreateCompilationWithAnalyzersAsync()
6163
{
62-
var projectAnalyzers = stateSets.SelectAsArray(s => !s.IsHostAnalyzer, s => s.Analyzer);
63-
var hostAnalyzers = stateSets.SelectAsArray(s => s.IsHostAnalyzer, s => s.Analyzer);
64+
var projectAnalyzers = analyzers.WhereAsArray(static (s, info) => !info.IsHostAnalyzer(s), hostAnalyzerInfo);
65+
var hostAnalyzers = analyzers.WhereAsArray(static (s, info) => info.IsHostAnalyzer(s), hostAnalyzerInfo);
6466

6567
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
6668

src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,20 @@ internal partial class DiagnosticAnalyzerService
2121
private partial class DiagnosticIncrementalAnalyzer
2222
{
2323
/// <summary>
24-
/// Return all diagnostics that belong to given project for the given StateSets (analyzers) either from cache or by calculating them
24+
/// Return all diagnostics that belong to given project for the given <see cref="DiagnosticAnalyzer"/> either
25+
/// from cache or by calculating them.
2526
/// </summary>
2627
private async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ComputeDiagnosticAnalysisResultsAsync(
27-
CompilationWithAnalyzersPair? compilationWithAnalyzers, Project project, ImmutableArray<StateSet> stateSets, CancellationToken cancellationToken)
28+
CompilationWithAnalyzersPair? compilationWithAnalyzers,
29+
Project project,
30+
ImmutableArray<DiagnosticAnalyzer> analyzers,
31+
CancellationToken cancellationToken)
2832
{
29-
using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, stateSets, cancellationToken))
33+
using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, analyzers, cancellationToken))
3034
{
3135
try
3236
{
33-
var result = await ComputeDiagnosticsForStateSetsAsync(stateSets).ConfigureAwait(false);
37+
var result = await ComputeDiagnosticsForIDEAnalyzersAsync(analyzers).ConfigureAwait(false);
3438

3539
// If project is not loaded successfully, get rid of any semantic errors from compiler analyzer.
3640
// Note: In the past when project was not loaded successfully we did not run any analyzers on the project.
@@ -111,12 +115,12 @@ async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> Co
111115
}
112116
}
113117

114-
async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ComputeDiagnosticsForStateSetsAsync(
115-
ImmutableArray<StateSet> stateSets)
118+
async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ComputeDiagnosticsForIDEAnalyzersAsync(
119+
ImmutableArray<DiagnosticAnalyzer> analyzers)
116120
{
117121
try
118122
{
119-
var ideAnalyzers = stateSets.Select(s => s.Analyzer).Where(a => a is ProjectDiagnosticAnalyzer or DocumentDiagnosticAnalyzer).ToImmutableArrayOrEmpty();
123+
var ideAnalyzers = analyzers.WhereAsArray(a => a is ProjectDiagnosticAnalyzer or DocumentDiagnosticAnalyzer);
120124

121125
return await ComputeDiagnosticsForAnalyzersAsync(ideAnalyzers).ConfigureAwait(false);
122126
}
Lines changed: 58 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,24 @@ private partial class DiagnosticIncrementalAnalyzer
1717
{
1818
private partial class StateManager
1919
{
20-
private HostAnalyzerStateSets GetOrCreateHostStateSets(Project project, ProjectAnalyzerStateSets projectStateSets)
20+
private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo(Project project, ProjectAnalyzerInfo projectAnalyzerInfo)
2121
{
22-
var key = new HostAnalyzerStateSetKey(project.Language, project.State.HasSdkCodeStyleAnalyzers, project.Solution.SolutionState.Analyzers.HostAnalyzerReferences);
22+
var key = new HostAnalyzerInfoKey(project.Language, project.State.HasSdkCodeStyleAnalyzers, project.Solution.SolutionState.Analyzers.HostAnalyzerReferences);
2323
// Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the
2424
// Host fallback options. These ids will be used when building up the Host and Project analyzer collections.
2525
var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(project);
26-
var hostStateSets = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (project.Solution.SolutionState.Analyzers, referenceIdsToRedirect));
27-
return hostStateSets.WithExcludedAnalyzers(projectStateSets.SkippedAnalyzersInfo.SkippedAnalyzers);
26+
var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (project.Solution.SolutionState.Analyzers, referenceIdsToRedirect));
27+
return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers);
2828

29-
static HostAnalyzerStateSets CreateLanguageSpecificAnalyzerMap(HostAnalyzerStateSetKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet<object> ReferenceIdsToRedirect) state)
29+
static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet<object> ReferenceIdsToRedirect) state)
3030
{
3131
var language = arg.Language;
3232
var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language);
3333

3434
var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect);
35-
var analyzerMap = CreateStateSetMap(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true);
35+
var (hostAnalyzers, allAnalyzers) = PartitionAnalyzers(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true);
3636

37-
return new HostAnalyzerStateSets(analyzerMap);
37+
return new HostAnalyzerInfo(hostAnalyzers, allAnalyzers);
3838
}
3939

4040
static (IEnumerable<ImmutableArray<DiagnosticAnalyzer>> HostAnalyzerCollection, IEnumerable<ImmutableArray<DiagnosticAnalyzer>> ProjectAnalyzerCollection) GetAnalyzerCollections(
@@ -90,68 +90,65 @@ static ImmutableHashSet<object> GetFeaturesAnalyzerReferenceIds(HostDiagnosticAn
9090
return builder.ToImmutable();
9191
}
9292
}
93+
}
94+
}
9395

94-
private sealed class HostAnalyzerStateSets
95-
{
96-
private const int FileContentLoadAnalyzerPriority = -4;
97-
private const int GeneratorDiagnosticsPlaceholderAnalyzerPriority = -3;
98-
private const int BuiltInCompilerPriority = -2;
99-
private const int RegularDiagnosticAnalyzerPriority = -1;
100-
101-
// ordered by priority
102-
public readonly ImmutableArray<StateSet> OrderedStateSets;
103-
104-
public readonly ImmutableDictionary<DiagnosticAnalyzer, StateSet> StateSetMap;
105-
106-
private HostAnalyzerStateSets(ImmutableDictionary<DiagnosticAnalyzer, StateSet> stateSetMap, ImmutableArray<StateSet> orderedStateSets)
107-
{
108-
StateSetMap = stateSetMap;
109-
OrderedStateSets = orderedStateSets;
110-
}
96+
private sealed class HostAnalyzerInfo
97+
{
98+
private const int FileContentLoadAnalyzerPriority = -4;
99+
private const int GeneratorDiagnosticsPlaceholderAnalyzerPriority = -3;
100+
private const int BuiltInCompilerPriority = -2;
101+
private const int RegularDiagnosticAnalyzerPriority = -1;
102+
103+
private readonly ImmutableHashSet<DiagnosticAnalyzer> _hostAnalyzers;
104+
private readonly ImmutableHashSet<DiagnosticAnalyzer> _allAnalyzers;
105+
public readonly ImmutableArray<DiagnosticAnalyzer> OrderedAllAnalyzers;
106+
107+
public HostAnalyzerInfo(
108+
ImmutableHashSet<DiagnosticAnalyzer> hostAnalyzers,
109+
ImmutableHashSet<DiagnosticAnalyzer> allAnalyzers)
110+
{
111+
_hostAnalyzers = hostAnalyzers;
112+
_allAnalyzers = allAnalyzers;
111113

112-
public HostAnalyzerStateSets(ImmutableDictionary<DiagnosticAnalyzer, StateSet> analyzerMap)
113-
{
114-
StateSetMap = analyzerMap;
114+
// order analyzers.
115+
// order will be in this order
116+
// BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers
117+
OrderedAllAnalyzers = [.. _allAnalyzers.OrderBy(PriorityComparison)];
118+
}
115119

116-
// order statesets
117-
// order will be in this order
118-
// BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers
119-
OrderedStateSets = [.. StateSetMap.Values.OrderBy(PriorityComparison)];
120-
}
120+
public bool IsHostAnalyzer(DiagnosticAnalyzer analyzer)
121+
=> _hostAnalyzers.Contains(analyzer);
121122

122-
public HostAnalyzerStateSets WithExcludedAnalyzers(ImmutableHashSet<DiagnosticAnalyzer> excludedAnalyzers)
123-
{
124-
if (excludedAnalyzers.IsEmpty)
125-
{
126-
return this;
127-
}
123+
public HostAnalyzerInfo WithExcludedAnalyzers(ImmutableHashSet<DiagnosticAnalyzer> excludedAnalyzers)
124+
{
125+
if (excludedAnalyzers.IsEmpty)
126+
{
127+
return this;
128+
}
128129

129-
var stateSetMap = StateSetMap.Where(kvp => !excludedAnalyzers.Contains(kvp.Key)).ToImmutableDictionary();
130-
var orderedStateSets = OrderedStateSets.WhereAsArray(stateSet => !excludedAnalyzers.Contains(stateSet.Analyzer));
131-
return new HostAnalyzerStateSets(stateSetMap, orderedStateSets);
132-
}
130+
return new(_hostAnalyzers, _allAnalyzers.Except(excludedAnalyzers));
131+
}
133132

134-
private int PriorityComparison(StateSet state1, StateSet state2)
135-
=> GetPriority(state1) - GetPriority(state2);
133+
private int PriorityComparison(DiagnosticAnalyzer state1, DiagnosticAnalyzer state2)
134+
=> GetPriority(state1) - GetPriority(state2);
136135

137-
private static int GetPriority(StateSet state)
138-
{
139-
// compiler gets highest priority
140-
if (state.Analyzer.IsCompilerAnalyzer())
141-
{
142-
return BuiltInCompilerPriority;
143-
}
144-
145-
return state.Analyzer switch
146-
{
147-
FileContentLoadAnalyzer _ => FileContentLoadAnalyzerPriority,
148-
GeneratorDiagnosticsPlaceholderAnalyzer _ => GeneratorDiagnosticsPlaceholderAnalyzerPriority,
149-
DocumentDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority),
150-
ProjectDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority),
151-
_ => RegularDiagnosticAnalyzerPriority,
152-
};
153-
}
136+
private static int GetPriority(DiagnosticAnalyzer state)
137+
{
138+
// compiler gets highest priority
139+
if (state.IsCompilerAnalyzer())
140+
{
141+
return BuiltInCompilerPriority;
154142
}
143+
144+
return state switch
145+
{
146+
FileContentLoadAnalyzer _ => FileContentLoadAnalyzerPriority,
147+
GeneratorDiagnosticsPlaceholderAnalyzer _ => GeneratorDiagnosticsPlaceholderAnalyzerPriority,
148+
DocumentDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority),
149+
ProjectDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority),
150+
_ => RegularDiagnosticAnalyzerPriority,
151+
};
155152
}
156153
}
157154
}

0 commit comments

Comments
 (0)