-
Notifications
You must be signed in to change notification settings - Fork 470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CA1853: Address issues found in PR review #6767 #6791
Conversation
Codecov Report
@@ Coverage Diff @@
## main #6791 +/- ##
==========================================
+ Coverage 96.39% 96.41% +0.02%
==========================================
Files 1403 1399 -4
Lines 330954 332327 +1373
Branches 10889 10862 -27
==========================================
+ Hits 319023 320429 +1406
+ Misses 9196 9169 -27
+ Partials 2735 2729 -6 |
This is done in a separate commit, so that the actual changes can be seen in the next commit. This is because git would not recognize the rename if the changes were added as is. Note that this commit by itself is broken.
This combines the analyzers CA1853 and CA1868 to avoid code duplication, make CA1853 support the same range of cases as CA1868 and fix dotnet#6781.
@Youssef1313 @buyaa-n @sharwell What do you think about combining the analyzers for guarded calls? This would help to apply the feedback for the set analyzer to the dictionary analyzer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mpidash thank you for fixing this, overall LGTM
...ests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKeyTests.cs
Outdated
Show resolved
Hide resolved
...ests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKeyTests.cs
Outdated
Show resolved
Hide resolved
...ests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKeyTests.cs
Outdated
Show resolved
Hide resolved
...ests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKeyTests.cs
Outdated
Show resolved
Hide resolved
...ests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKeyTests.cs
Outdated
Show resolved
Hide resolved
...ests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKeyTests.cs
Outdated
Show resolved
Hide resolved
...ests/Microsoft.NetCore.Analyzers/Performance/DoNotGuardDictionaryRemoveByContainsKeyTests.cs
Outdated
Show resolved
Hide resolved
|
||
private void OnCompilationStart(CompilationStartAnalysisContext context) | ||
{ | ||
foreach (var guardedCallContext in s_guardedCallContexts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if you tried/considered to populate all symbols once and register one action to check all, might be more efficient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about this in testing as I had a bug where some guarded calls were found twice (two separate actions found them) but did not follow through.
Should I prepare a new PR where everything is consolidated into one action or should we keep it like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I prepare a new PR where everything is consolidated into one action
That would be great, thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested loading all symbols once and then registering one action like this:
private void OnCompilationStart(CompilationStartAnalysisContext context)
{
var guardedCallContextWithSymbols = s_guardedCallContexts
.Select(c => GuardedCallSymbols.TryGetSymbols(context.Compilation, c, out var symbols)
? (Context: c, Symbols: symbols)
: (Context: c, Symbols: null))
.WhereAsArray(p => p.Symbols is not null);
if (!guardedCallContextWithSymbols.IsEmpty)
{
context.RegisterOperationAction(OnConditional, OperationKind.Conditional);
void OnConditional(OperationAnalysisContext context)
{
foreach (var (guardedCallContext, symbols) in guardedCallContextWithSymbols)
{
var conditional = (IConditionalOperation)context.Operation;
if (!symbols!.HasApplicableConditionInvocation(conditional.Condition, out var conditionInvocation, out bool containsNegated) ||
!symbols.HasApplicableGuardedInvocation(conditional, containsNegated, out var guardedInvocation) ||
!AreInvocationsOnSameInstance(conditionInvocation, guardedInvocation) ||
!AreInvocationArgumentsEqual(conditionInvocation, guardedInvocation))
{
continue;
}
using var locations = ArrayBuilder<Location>.GetInstance(2);
locations.Add(conditional.Syntax.GetLocation());
locations.Add(guardedInvocation.Syntax.Parent!.GetLocation());
context.ReportDiagnostic(conditionInvocation.CreateDiagnostic(
guardedCallContext.Rule,
additionalLocations: locations.ToImmutable(),
properties: null,
guardedInvocation.TargetMethod.ToDisplayString(s_symbolDisplayFormat),
conditionInvocation.TargetMethod.ToDisplayString(s_symbolDisplayFormat)));
}
}
}
}
I've tried to measure the performance difference with AnalyzerRunner
. The variance between runs of the same code (old one and the updated above) was quite high on my PC (from 5s to 11s for both, tested against dotnet/roslyn
with a Release build), which makes it kinda hard to say for sure that one is more performant than the other.
Do you know a better way to compare the performance of two analyzers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know a better way to compare the performance of two analyzers?
I haven't really tried to measure a perf of analyzers, there is some suggestions in https://github.com/dotnet/roslyn-analyzers/blob/main/docs/Performance.md, you might have already checked that.
We also have some PerformanceTests in the repo, by my understanding that runs the tests on CI, saves the results somewhere and compares them with the next PR's perf build and fails if the diff was above the threshold, not sure if adding a test for the analyzer there and use the old version as a Base
could be a better way. The Perf.cmd script in the root would run all performance tests (which probably would take several hours to finish), unfortunately it seems there is no way to run only one specific perf test
Ping @mavasani for better suggestions
The implementation of CA1868 (
Unnecessary call to 'Contains' for sets
) was based on the analyzer CA1853 (Unnecessary call to 'Dictionary.ContainsKey(key)
), since they are very similar. During the review of #6767, a couple of problems were found that should also be addressed for CA1853.This PR merges the analyzer for guarded calls, leading to the following changes for CA1853:
ContainsKey
andRemove
differ #6781SortedDictionary<TKey, TValue>
orImmutableSortedDictionary<TKey, TValue>.Builder
)IDictionary<TKey, TValue>
)IImmutableDictionary<TKey, TValue>
and its derived typesSystem.Collections.Generic.CollectionExtensions.Remove(IDictionary<TKey, TValue>, TKey, out TValue)
forSortedDictionary<TKey, TValue>
(asRemove(TKey, out TValue
is part of the concrete typeDictionary<TKey, TValue>
))This should also make it easier to add other guarded call diagnostics in the future.