Skip to content

Commit c1716fa

Browse files
Always use .OriginalDefinition uniformly in the unread-members analyzer
1 parent a07d8af commit c1716fa

File tree

1 file changed

+74
-52
lines changed

1 file changed

+74
-52
lines changed

src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs

Lines changed: 74 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,32 @@ protected virtual void HandleNamedTypeSymbolStart(SymbolStartAnalysisContext con
9090
{
9191
}
9292

93+
internal sealed class OriginalDefinitionSymbolEqualityComparer : IEqualityComparer<ISymbol>
94+
{
95+
public static readonly OriginalDefinitionSymbolEqualityComparer Instance = new();
96+
97+
private OriginalDefinitionSymbolEqualityComparer()
98+
{
99+
}
100+
101+
bool IEqualityComparer<ISymbol>.Equals(ISymbol? x, ISymbol? y)
102+
=> Equals(x?.OriginalDefinition, y?.OriginalDefinition);
103+
104+
int IEqualityComparer<ISymbol>.GetHashCode(ISymbol obj)
105+
=> obj is null ? 0 : obj.OriginalDefinition.GetHashCode();
106+
}
107+
93108
private sealed class CompilationAnalyzer
94109
{
95110
private readonly object _gate = new();
96111

112+
private static readonly ObjectPool<HashSet<ISymbol>> s_originalDefinitionSymbolHashSetPool = new(() => new(OriginalDefinitionSymbolEqualityComparer.Instance));
113+
97114
/// <summary>
98115
/// State map for candidate member symbols, with the value indicating how each symbol is used in executable code.
99116
/// </summary>
100-
private readonly Dictionary<ISymbol, ValueUsageInfo> _symbolValueUsageStateMap = [];
117+
private readonly Dictionary<ISymbol, ValueUsageInfo> _symbolValueUsageStateMap_doNotAccessDirectly = new(OriginalDefinitionSymbolEqualityComparer.Instance);
118+
101119
/// <summary>
102120
/// List of properties that have a 'get' accessor usage, while the value itself is not used, e.g.:
103121
/// <code>
@@ -109,7 +127,7 @@ private sealed class CompilationAnalyzer
109127
/// </code>
110128
/// Here, 'get' accessor is used in an increment operation, but the result of the increment operation isn't used and 'P' itself is not used anywhere else, so it can be safely removed
111129
/// </summary>
112-
private readonly HashSet<IPropertySymbol> _propertiesWithShadowGetAccessorUsages = [];
130+
private readonly HashSet<IPropertySymbol> _propertiesWithShadowGetAccessorUsages = new(OriginalDefinitionSymbolEqualityComparer.Instance);
113131
private readonly INamedTypeSymbol? _taskType;
114132
private readonly INamedTypeSymbol? _genericTaskType;
115133
private readonly INamedTypeSymbol? _debuggerDisplayAttributeType;
@@ -258,7 +276,7 @@ bool ShouldAnalyze(SymbolStartAnalysisContext context, INamedTypeSymbol namedTyp
258276
// Check if we have at least one candidate symbol in analysis scope.
259277
foreach (var member in namedType.GetMembers())
260278
{
261-
if (IsCandidateSymbol(member.OriginalDefinition)
279+
if (IsCandidateSymbol(member)
262280
&& context.ShouldAnalyzeLocation(GetDiagnosticLocation(member)))
263281
{
264282
return true;
@@ -275,19 +293,55 @@ bool ShouldAnalyze(SymbolStartAnalysisContext context, INamedTypeSymbol namedTyp
275293

276294
private void AnalyzeSymbolDeclaration(SymbolAnalysisContext symbolContext)
277295
{
278-
var symbol = symbolContext.Symbol.OriginalDefinition;
296+
var symbol = symbolContext.Symbol;
279297
if (IsCandidateSymbol(symbol)
280298
&& symbolContext.ShouldAnalyzeLocation(GetDiagnosticLocation(symbol)))
281299
{
282-
lock (_gate)
300+
// Initialize unused state to 'ValueUsageInfo.None' to indicate that
301+
// no read/write references have been encountered yet for this symbol.
302+
// Note that we might receive a symbol reference (AnalyzeMemberOperation) callback before
303+
// this symbol declaration callback, so even though we cannot receive duplicate callbacks for a symbol,
304+
// an entry might already be present of the declared symbol here.
305+
AddSymbolUsage(symbol, ValueUsageInfo.None);
306+
}
307+
}
308+
309+
private void AddSymbolUsage(ISymbol? symbol, ValueUsageInfo info)
310+
{
311+
if (symbol is null)
312+
return;
313+
314+
lock (_gate)
315+
{
316+
_symbolValueUsageStateMap_doNotAccessDirectly.TryAdd(symbol, info);
317+
}
318+
}
319+
320+
private void UpdateSymbolUsage(ISymbol? symbol, ValueUsageInfo info)
321+
{
322+
if (symbol is null)
323+
return;
324+
325+
lock (_gate)
326+
{
327+
if (_symbolValueUsageStateMap_doNotAccessDirectly.TryGetValue(symbol, out var currentUsageInfo))
328+
info = currentUsageInfo | info;
329+
330+
_symbolValueUsageStateMap_doNotAccessDirectly[symbol] = info;
331+
}
332+
}
333+
334+
private bool TryGetAndRemoveSymbolUsage(ISymbol memberSymbol, out ValueUsageInfo valueUsageInfo)
335+
{
336+
lock (_gate)
337+
{
338+
if (_symbolValueUsageStateMap_doNotAccessDirectly.TryGetValue(memberSymbol, out valueUsageInfo))
283339
{
284-
// Initialize unused state to 'ValueUsageInfo.None' to indicate that
285-
// no read/write references have been encountered yet for this symbol.
286-
// Note that we might receive a symbol reference (AnalyzeMemberOperation) callback before
287-
// this symbol declaration callback, so even though we cannot receive duplicate callbacks for a symbol,
288-
// an entry might already be present of the declared symbol here.
289-
_symbolValueUsageStateMap.TryAdd(symbol, ValueUsageInfo.None);
340+
_symbolValueUsageStateMap_doNotAccessDirectly.Remove(memberSymbol);
341+
return true;
290342
}
343+
344+
return false;
291345
}
292346
}
293347

@@ -320,40 +374,15 @@ private void AnalyzeFieldInitializer(OperationAnalysisContext operationContext)
320374
private void OnSymbolUsage(ISymbol? memberSymbol, ValueUsageInfo usageInfo)
321375
{
322376
if (!IsCandidateSymbol(memberSymbol))
323-
{
324377
return;
325-
}
326-
327-
lock (_gate)
328-
{
329-
// Update the usage info for the memberSymbol
330-
if (_symbolValueUsageStateMap.TryGetValue(memberSymbol, out var currentUsageInfo))
331-
{
332-
usageInfo = currentUsageInfo | usageInfo;
333-
}
334378

335-
_symbolValueUsageStateMap[memberSymbol] = usageInfo;
336-
}
337-
}
338-
339-
private bool TryRemove(ISymbol memberSymbol, out ValueUsageInfo valueUsageInfo)
340-
{
341-
lock (_gate)
342-
{
343-
if (_symbolValueUsageStateMap.TryGetValue(memberSymbol, out valueUsageInfo))
344-
{
345-
_symbolValueUsageStateMap.Remove(memberSymbol);
346-
return true;
347-
}
348-
349-
return false;
350-
}
379+
UpdateSymbolUsage(memberSymbol, usageInfo);
351380
}
352381

353382
private void AnalyzeMemberReferenceOperation(OperationAnalysisContext operationContext)
354383
{
355384
var memberReference = (IMemberReferenceOperation)operationContext.Operation;
356-
var memberSymbol = memberReference.Member.OriginalDefinition;
385+
var memberSymbol = memberReference.Member;
357386
if (IsCandidateSymbol(memberSymbol))
358387
{
359388
// Get the value usage info.
@@ -421,16 +450,11 @@ private void AnalyzeLoopOperation(OperationAnalysisContext operationContext)
421450

422451
private void AnalyzeInvocationOperation(OperationAnalysisContext operationContext)
423452
{
424-
var targetMethod = ((IInvocationOperation)operationContext.Operation).TargetMethod.OriginalDefinition;
453+
var targetMethod = ((IInvocationOperation)operationContext.Operation).TargetMethod;
425454

426455
// A method invocation is considered as a read reference to the symbol
427456
// to ensure that we consider the method as "used".
428457
OnSymbolUsage(targetMethod, ValueUsageInfo.Read);
429-
430-
// If the invoked method is a reduced extension method, also mark the original
431-
// method from which it was reduced as "used".
432-
if (targetMethod.ReducedFrom != null)
433-
OnSymbolUsage(targetMethod.ReducedFrom, ValueUsageInfo.Read);
434458
}
435459

436460
private void AnalyzeNameOfOperation(OperationAnalysisContext operationContext)
@@ -444,7 +468,7 @@ private void AnalyzeNameOfOperation(OperationAnalysisContext operationContext)
444468

445469
if (nameofArgument is IMemberReferenceOperation memberReference)
446470
{
447-
OnSymbolUsage(memberReference.Member.OriginalDefinition, ValueUsageInfo.ReadWrite);
471+
OnSymbolUsage(memberReference.Member, ValueUsageInfo.ReadWrite);
448472
return;
449473
}
450474

@@ -453,12 +477,12 @@ private void AnalyzeNameOfOperation(OperationAnalysisContext operationContext)
453477
// a bound method group/property group.
454478
var symbolInfo = nameofArgument.SemanticModel!.GetSymbolInfo(nameofArgument.Syntax, operationContext.CancellationToken);
455479
foreach (var symbol in symbolInfo.GetAllSymbols())
456-
OnSymbolUsage(symbol.OriginalDefinition, ValueUsageInfo.ReadWrite);
480+
OnSymbolUsage(symbol, ValueUsageInfo.ReadWrite);
457481
}
458482

459483
private void AnalyzeObjectCreationOperation(OperationAnalysisContext operationContext)
460484
{
461-
var constructor = ((IObjectCreationOperation)operationContext.Operation).Constructor?.OriginalDefinition;
485+
var constructor = ((IObjectCreationOperation)operationContext.Operation).Constructor;
462486

463487
// An object creation is considered as a read reference to the constructor
464488
// to ensure that we consider the constructor as "used".
@@ -481,7 +505,7 @@ private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasUnsuppo
481505
// Report diagnostics for unused candidate members.
482506
var first = true;
483507

484-
using var _1 = PooledHashSet<ISymbol>.GetInstance(out var symbolsReferencedInDocComments);
508+
using var _1 = s_originalDefinitionSymbolHashSetPool.GetPooledObject(out var symbolsReferencedInDocComments);
485509
using var _2 = ArrayBuilder<string>.GetInstance(out var debuggerDisplayAttributeArguments);
486510

487511
var entryPoint = symbolEndContext.Compilation.GetEntryPoint(cancellationToken);
@@ -499,7 +523,7 @@ private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasUnsuppo
499523

500524
// Check if the underlying member is neither read nor a readable reference to the member is taken.
501525
// If so, we flag the member as either unused (never written) or unread (written but not read).
502-
if (TryRemove(member, out var valueUsageInfo) && !valueUsageInfo.IsReadFrom())
526+
if (TryGetAndRemoveSymbolUsage(member, out var valueUsageInfo) && !valueUsageInfo.IsReadFrom())
503527
{
504528
Debug.Assert(IsCandidateSymbol(member));
505529
Debug.Assert(!member.IsImplicitlyDeclared);
@@ -649,7 +673,7 @@ private void AddCandidateSymbolsReferencedInDocComments(
649673
foreach (var node in docComment.DescendantNodes().OfType<TIdentifierNameSyntax>())
650674
{
651675
lazyModel ??= compilation.GetSemanticModel(syntaxTree);
652-
var symbol = lazyModel.GetSymbolInfo(node, cancellationToken).Symbol?.OriginalDefinition;
676+
var symbol = lazyModel.GetSymbolInfo(node, cancellationToken).Symbol;
653677

654678
if (IsCandidateSymbol(symbol))
655679
builder.Add(symbol);
@@ -757,8 +781,6 @@ private bool IsCandidateSymbol([NotNullWhen(true)] ISymbol? memberSymbol)
757781
if (memberSymbol is null)
758782
return false;
759783

760-
Debug.Assert(memberSymbol == memberSymbol.OriginalDefinition);
761-
762784
if (memberSymbol.DeclaredAccessibility == Accessibility.Private &&
763785
!memberSymbol.IsImplicitlyDeclared)
764786
{

0 commit comments

Comments
 (0)