-
Notifications
You must be signed in to change notification settings - Fork 468
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
Add the UseConreteType analyzer #6370
Conversation
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #6370 +/- ##
==========================================
+ Coverage 96.12% 96.14% +0.02%
==========================================
Files 1361 1365 +4
Lines 316084 317413 +1329
Branches 10183 10263 +80
==========================================
+ Hits 303841 305187 +1346
+ Misses 9814 9790 -24
- Partials 2429 2436 +7 |
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
...tAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.State.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.State.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
...tAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.cs
Outdated
Show resolved
Hide resolved
...tAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs
Show resolved
Hide resolved
...tAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs
Outdated
Show resolved
Hide resolved
...tAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/UseConcreteTypeAnalyzer.Collector.cs
Outdated
Show resolved
Hide resolved
Thank you @geeknoid, I run the analyzer in runtime repo and it is found 544 diagnostics for one build (over 3000 for all platforms build). With that many diagnostics it would be very useful to have a fixer. Do you plan to add a fixer in this PR or later? I take a look some of the diagnostics (the ones found in System.Private.CoreLib) and suspect some of the are false positives:
I have attached the findings for reference: CA1859-log.txt |
|
||
public class Test | ||
{ | ||
private void Method(IFoo {|#0:foo|}) |
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.
It would be better to squiggle below the symbol that needs an action, i.e. it would make more sense to squiggle under IFoo
not foo
( {|#0:IFoo|} foo
)
|
||
public class Test | ||
{ | ||
private IFoo? {|#0:Method1|}(FooProvider? provider) |
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.
Same here and others places, private {|#0:IFoo|}? Method1(FooProvider? provider)
not private IFoo? {|#0:Method1|}(FooProvider? provider)
As per dotnet/runtime#51193. This recommends using concrete types for fields, local, parameters, and method returns instead of interface/abstract types when possible and when it doesn't affect the API surface of a class. The idea is to help eliminate virtual/interface dispatch, while also making available potentially richer APIs exposed by implementations relative to their interface.
@buyaa-n Thanks for the detailed test and report. I tried each case you highlighted. Here's my input in order:
I've updated the PR to fix case 3. I could use some help for cases 1 and 2 however. I think I don't understand how Roslyn deals with generic types, and this is leading to both false positives and false negatives. If you look my test cases called ShouldTrigger1 and ShouldTrigger2, these should lead to a diagnostic about upgrading the parameter of the Do method to be of type Foo or Foo. But it's not firing. I have a list of candidate parameters which could be upgraded, which includes the parameter "foo" of the Do method. Now I also have a list of the different types assigned to each parameter by all the call sites. The problem is that when I use SymbolComparer.Default.Equals to compare those two (identical!) parameters, I am told they are different. If I compare the two parameter instances by hand, comparing each field, they seem to be identical. Yet Roslyn is claiming otherwise. If I take the generics out of the picture, this kind of code triggers just fine. I've just spent a couple hours on this and made no progress. I could use some help. Thanks |
@buyaa-n To answer your question, I'm not currently planning a fixer for this. I unfortunately don't have the bandwidth for that at this point. |
@geeknoid This normally indicates that you are trying to compare a symbol reference with a symbol definition. The solution in most cases should be to use |
Thanks @mavasani , that did the trick! @buyaa-n I've updated the PR which will hopefully address the false positives. That just leaves reporting the squiggles on the type name, rather than on the symbol name. I'm going to have to think about that, I think it might require a fair bit of change to how the code works now. But maybe I can find some clever way to do it. |
public INamedTypeSymbol? Void { get; private set; } | ||
|
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.
public INamedTypeSymbol? Void { get; private set; } |
Void = null; | ||
|
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.
Void = null; |
var c = _pool.Allocate(); | ||
c.Void = compilation.GetSpecialType(SpecialType.System_Void); | ||
return c; |
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.
var c = _pool.Allocate(); | |
c.Void = compilation.GetSpecialType(SpecialType.System_Void); | |
return c; | |
return _pool.Allocate(); |
context.RegisterSymbolStartAction(context => | ||
{ | ||
var coll = Collector.GetInstance(context.Compilation); | ||
|
||
context.RegisterOperationAction(context => coll.HandleInvocation((IInvocationOperation)context.Operation), OperationKind.Invocation); | ||
context.RegisterOperationAction(context => coll.HandleSimpleAssignment((ISimpleAssignmentOperation)context.Operation), OperationKind.SimpleAssignment); | ||
context.RegisterOperationAction(context => coll.HandleCoalesceAssignment((ICoalesceAssignmentOperation)context.Operation), OperationKind.CoalesceAssignment); | ||
context.RegisterOperationAction(context => coll.HandleDeconstructionAssignment((IDeconstructionAssignmentOperation)context.Operation), OperationKind.DeconstructionAssignment); | ||
context.RegisterOperationAction(context => coll.HandleFieldInitializer((IFieldInitializerOperation)context.Operation), OperationKind.FieldInitializer); | ||
context.RegisterOperationAction(context => coll.HandleVariableDeclarator((IVariableDeclaratorOperation)context.Operation), OperationKind.VariableDeclarator); | ||
context.RegisterOperationAction(context => coll.HandleDeclarationExpression((IDeclarationExpressionOperation)context.Operation), OperationKind.DeclarationExpression); | ||
context.RegisterOperationAction(context => coll.HandleReturn((IReturnOperation)context.Operation), OperationKind.Return); | ||
|
||
context.RegisterSymbolEndAction(context => | ||
{ | ||
Report(context, coll); | ||
Collector.ReturnInstance(coll, context.CancellationToken); | ||
}); | ||
}, SymbolKind.NamedType); |
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.
context.RegisterSymbolStartAction(context => | |
{ | |
var coll = Collector.GetInstance(context.Compilation); | |
context.RegisterOperationAction(context => coll.HandleInvocation((IInvocationOperation)context.Operation), OperationKind.Invocation); | |
context.RegisterOperationAction(context => coll.HandleSimpleAssignment((ISimpleAssignmentOperation)context.Operation), OperationKind.SimpleAssignment); | |
context.RegisterOperationAction(context => coll.HandleCoalesceAssignment((ICoalesceAssignmentOperation)context.Operation), OperationKind.CoalesceAssignment); | |
context.RegisterOperationAction(context => coll.HandleDeconstructionAssignment((IDeconstructionAssignmentOperation)context.Operation), OperationKind.DeconstructionAssignment); | |
context.RegisterOperationAction(context => coll.HandleFieldInitializer((IFieldInitializerOperation)context.Operation), OperationKind.FieldInitializer); | |
context.RegisterOperationAction(context => coll.HandleVariableDeclarator((IVariableDeclaratorOperation)context.Operation), OperationKind.VariableDeclarator); | |
context.RegisterOperationAction(context => coll.HandleDeclarationExpression((IDeclarationExpressionOperation)context.Operation), OperationKind.DeclarationExpression); | |
context.RegisterOperationAction(context => coll.HandleReturn((IReturnOperation)context.Operation), OperationKind.Return); | |
context.RegisterSymbolEndAction(context => | |
{ | |
Report(context, coll); | |
Collector.ReturnInstance(coll, context.CancellationToken); | |
}); | |
}, SymbolKind.NamedType); | |
context.RegisterCompilationStartAction(context => | |
{ | |
var voidType = context.Compilation.GetSpecialType(SpecialType.System_Void); | |
context.RegisterSymbolStartAction(context => | |
{ | |
var coll = Collector.GetInstance(context.Compilation); | |
context.RegisterOperationAction(context => coll.HandleInvocation((IInvocationOperation)context.Operation), OperationKind.Invocation); | |
context.RegisterOperationAction(context => coll.HandleSimpleAssignment((ISimpleAssignmentOperation)context.Operation), OperationKind.SimpleAssignment); | |
context.RegisterOperationAction(context => coll.HandleCoalesceAssignment((ICoalesceAssignmentOperation)context.Operation), OperationKind.CoalesceAssignment); | |
context.RegisterOperationAction(context => coll.HandleDeconstructionAssignment((IDeconstructionAssignmentOperation)context.Operation), OperationKind.DeconstructionAssignment); | |
context.RegisterOperationAction(context => coll.HandleFieldInitializer((IFieldInitializerOperation)context.Operation), OperationKind.FieldInitializer); | |
context.RegisterOperationAction(context => coll.HandleVariableDeclarator((IVariableDeclaratorOperation)context.Operation), OperationKind.VariableDeclarator); | |
context.RegisterOperationAction(context => coll.HandleDeclarationExpression((IDeclarationExpressionOperation)context.Operation), OperationKind.DeclarationExpression); | |
context.RegisterOperationAction(context => coll.HandleReturn((IReturnOperation)context.Operation), OperationKind.Return); | |
context.RegisterSymbolEndAction(context => | |
{ | |
Report(context, coll, voidType); | |
Collector.ReturnInstance(coll, context.CancellationToken); | |
}); | |
}, SymbolKind.NamedType); | |
} |
/// <summary> | ||
/// Given all the accumulated analysis state, generate the diagnostics. | ||
/// </summary> | ||
private static void Report(SymbolAnalysisContext context, Collector coll) |
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.
private static void Report(SymbolAnalysisContext context, Collector coll) | |
private static void Report(SymbolAnalysisContext context, Collector coll, INamedTypeSymbol voidType) |
{ | ||
using var types = PooledHashSet<ITypeSymbol>.GetInstance(assignments, SymbolEqualityComparer.Default); | ||
|
||
var assignedNull = types.Remove(coll.Void!); |
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.
var assignedNull = types.Remove(coll.Void!); | |
var assignedNull = types.Remove(voidType); |
OK, thank you for letting us know. We can add that later, I will create an issue for fixer implementation and put it up for grabs.
Thank you, there is one analyzer that I know did that: Lines 440 to 449 in 60f592c
|
@Youssef1313 Unless I'm missing something, I think your suggestions of removing the Void property from the collector type doesn't work. Line 333 of the Collector.cs file needs this value, that's why it was present in the object in the first place. |
I'm getting the void type once in compilation start and passing it down with these suggestions. Yes I missed this usage, but the idea is the same. |
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.
Overall LGTM, thank you!
Created an issue for fixer and diagnostic location. Merging the PR Merging the PR, @Youssef1313 your suggestions makes sense to me, but as @geeknoid mentioned above applying your suggestions on the PR directly might fail the build, feel free to raise a PR with your suggestions after merge, thank you! |
@buyaa-n Thanks! |
I'm trying this out on dotnet/runtime, and I'm getting these warnings:
That's for these public methods on a public type: Is it a bug that these are being reported? I thought the intent was to only fire for internal/private? I tried adding:
and it didn't help. |
These are bugs. Are these the only ones showing up for you? It's not supposed to fire on public signatures. I specifically tried to repro these two and it's just not happening from within the test environment. I'm wondering if there's something funny going on because its corelib. This rule has been running for a year on hundreds of projects and this hasn't surfaced before, so there's something peculiar about those two methods. |
Oops I thought those were fixed, @stephentoub would you suggest reverting this PR or can we fix this with different PR? |
The only public ones for corelib. I've not yet tried beyond that.
No need to revert. Separate PR once the issue is diagnosed is fine. |
What magic do I need to do to run the analyzer against core lib in such a way that I can continuously run the analyzer while deleting code from MemoryExtensions.cs? I need to create a reproducible example. I've got a top-level condition to not produce diagnostics against public symbols, and somehow this is slipping through. |
Aha, never mind. I have a repro. The problem emerges when functions assign values to their own incoming parameters. The analyzer is getting confused about what this means. |
As a follow-up to this, I realized I never created docs for this. So here's a PR: dotnet/docs#35182 |
As per dotnet/runtime#51193. This recommends using concrete types for fields, local, parameters, and method returns instead of interface/abstract types when possible and when it doesn't affect the API surface of a class. The idea is to help eliminate virtual/interface dispatch, while also making available potentially richer APIs exposed by implementations relative to their interface.