-
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
(4/4) Prefer 'AsSpan' over 'Substring' analyzer #4806
(4/4) Prefer 'AsSpan' over 'Substring' analyzer #4806
Conversation
Add 'GetFirstOrDefaultMemberWithParameterTypes' helper method
…refer-AsSpan-over-Substring-analyzer
Codecov Report
@@ Coverage Diff @@
## release/6.0.1xx #4806 +/- ##
===================================================
+ Coverage 95.58% 95.59% +0.01%
===================================================
Files 1215 1220 +5
Lines 278104 279856 +1752
Branches 16768 16815 +47
===================================================
+ Hits 265824 267529 +1705
- Misses 10037 10077 +40
- Partials 2243 2250 +7 |
Is this a common case? I would typically prefer one of the following:
|
Sounds reasonable. I don't think it's super common, so I'll do this for now, unless we find evidence that it's a common scenario. |
Previously, fixer would only fix cases where all Substring calls could be converted. Now, it fixes all cases where there is a single unambiguous best overload.
- Handle cases where not every Substring invocation can be converted to AsSpan - Add tests for inaccessible overloads
@sharwell I've implemented it so that ambiguous cases are reported, but not fixed. |
Fix issue where 'System' namespace would be imported unnecessarily when violations were inside a System namespace declaration, or where the System import was inside a namespace declaration. This issue was encountered in dotnet/runtime
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.Fixer.cs
Outdated
Show resolved
Hide resolved
private protected override void ReplaceInvocationMethodName(SyntaxEditor editor, SyntaxNode memberInvocation, string newName) | ||
{ | ||
var cast = (InvocationExpressionSyntax)memberInvocation; | ||
var memberAccessSyntax = (MemberAccessExpressionSyntax)cast.Expression; |
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.
Are we sure we can't be on something else than MemberAccessExpresionSyntax
here? Maybe a member binding or something 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.
The only way this could be a MemberBindingExpressionSyntax
is if the substring invocation is conditional, foo?.Substring(1)
.
This can't possibly be true because the analyzer detects IArgumentOperation
s that are IInvocationOperation
s (after stripping implicit conversions), and the syntax of foo?.Substring(1)
is a ConditionalAccessExpressionSyntax
, and not an InvocationExpressionSyntax
.
I'll add some tests ensuring the analyzer does not report conditional substring invocations, and I'll add some assertions and comments to document this invariant.
I'll also rename the method to ReplaceNonConditionalInvocationMethodName
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Outdated
Show resolved
Hide resolved
|
||
context.RegisterOperationBlockStartAction(OnOperationBlockStart); | ||
|
||
void OnOperationBlockStart(OperationBlockStartAnalysisContext context) |
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.
[minor] Method could be made static
.
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 captures symbols
(used on line 63).
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.
My bad, it's not easy to see captured variables in github.
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
internal static IOperation WalkDownImplicitConversions(IOperation operation) |
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 am wondering if we should update the WalkDownConversions
helper to have an optional predicate function Func<IConversionOperation, bool>
.
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 think that would be a good idea. I've wanted that function on several other analyzers. While we're talking about upgrading helpers, I've also wanted a GetFirstOrDefaultWithParamater
overload that takes ITypeSymbol
s instead of ParameterInfo
s.
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Outdated
Show resolved
Hide resolved
-Inline diagnostic descriptors -Add WalkDownConversion overload that takes a predicate. -Use more descriptive names for RequiredSymbols members.
@@ -12,7 +12,7 @@ | |||
Design: CA2210, CA1000-CA1070 | |||
Globalization: CA2101, CA1300-CA1310 | |||
Mobility: CA1600-CA1601 | |||
Performance: HA, CA1800-CA1838 | |||
Performance: HA, CA1800-CA1842 |
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.
Why the jump from 38 to 42?
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.
Because my other analyzer PRs use CA1839-CA1841. I wasn't sure how it's typically handled when there are multiple outstanding PRs that both want to add an analyzer to the same diagnostic category.
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.
Makes sense, maybe you could add a [X/Y] prefix on your PRs so that we merge them in the right order.
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 added prefixes here (1/4), here (2/4), here (3/4), and this one is 4/4. I think some of them may be in different milestones. Do you want me to change the IDs around so that earlier milestones get merged first?
src/Utilities/Compiler/Extensions/IEnumerableOfIMethodSymbolExtensions.cs
Show resolved
Hide resolved
src/Utilities/Compiler/Extensions/IEnumerableOfIMethodSymbolExtensions.cs
Show resolved
Hide resolved
/// <param name="operation">The starting operation.</param> | ||
/// <param name="predicate">A predicate to filter conversion operations.</param> | ||
/// <returns>The first operation that either isn't a conversion or doesn't satisfy <paramref name="predicate"/>.</returns> | ||
public static IOperation WalkDownConversion(this IOperation operation, Func<IConversionOperation, bool> predicate) |
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.
Maybe this could be merged with the previous and allow nullable Func
. Note that I am not convinced whether it brings much.
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 did end up needing it on one of my other PRs. I can see it being useful in the future, especially for dealing with arguments where you don't want to skip through explicit casts.
Co-authored-by: Amaury Levé <amaury.leve@gmail.com>
…refer-AsSpan-over-Substring-analyzer
…CoreAnalyzersResources.resx Co-authored-by: Manish Vasani <mavasani@microsoft.com>
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Show resolved
Hide resolved
} | ||
} | ||
|
||
private static IEnumerable<IMethodSymbol> GetAllAccessibleOverloadsIncludingSelf(IInvocationOperation invocation, CancellationToken cancellationToken) |
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.
Can we use/enhance/update this extension method?
public static IEnumerable<IMethodSymbol> GetOverloads(this IMethodSymbol? method) |
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.
That method is poorly named. Basically, it looks at a particular invocation operation and finds all overloads of the invoked method that would also be legal at that location, taking into account the type of the receiver (for instance methods) or type (for static methods). It's pretty specific to this analyzer; I can't make a version that uses an IMethodSymbol
instead of an IInvocationOperation
because it depends on the receiver of the invocation. I'll rename it to something more descriptive to make this clearer.
If we did want to add an extension method that does this, it couldn't go in IMethodSymbolExtensions
because the extended receiver would need to be IInvocationOperation
.
Edit: I've renamed the method to GetAllAccessibleOverloadsAtInvocationCallSite
to reduce confusion.
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.Fixer.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.Fixer.cs
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.Fixer.cs
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/PreferAsSpanOverSubstring.cs
Show resolved
Hide resolved
[MemberData(nameof(Data_NestedViolations))] | ||
public Task NestedViolations_AreAllReportedAndFixed_CS( | ||
string receiverClass, string testExpression, string fixedExpression, int[] locations, | ||
int? incrementalIterations = null, int? fixAllInDocumentIterations = null, int? fixAllIterations = 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.
All the yield return values in Data_NestedViolations
seems to provide non-null values for these 3 parameters. Can these parameters be marked non-optional?
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.
Also it seems fixAllInDocumentIterations
and fixAllIterations
is always 1. Do we need a parameter for these as all?
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 think at one point I had different values for these parameters. I'll mark them non-optional and remove fixAllInDocumentIterations
and fixAllIterations
, as they are always 1.
public void Consume(string a, Roschar b) { }"; | ||
yield return new[] | ||
{ | ||
CS.WithBody(WithKey(@"Consume(foo.Substring(1), foo.Substring(2))", 0) + ';', members) |
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.
We can potentially consider showing 2 code fixes here, one for each best candidate, but given this would be a rare case, current approach of not showing code fix is also reasonable.
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.
@mavasani I'm happy to implement that functionality. How do I write tests that expect two code fixes for one violation? Is there another analyzer that does this that I could look at as an example?
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.
@sharwell Might have suggestions on this - I don't believe we need this as part of this PR. Feel free to file a separate issue or open a follow-up PR for it.
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 looks great to me and extensive unit tests give a lot of confidence, thanks!
Have some minor comments - I can merge once they are resolved.
Co-authored-by: Manish Vasani <mavasani@microsoft.com>
@NewellClark Can you please resolve merge conflicts? |
Fixes issue #33784.
Violations found in dotnet/runtime:
System.Private.DataContractSerialization
(PR)System.Private.Xml
(PR)Edit: ambiguous cases are now reported, but not fixed.