@@ -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