-
Notifications
You must be signed in to change notification settings - Fork 457
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
Fix NRE in BasicDoNotUseCountWhenAnyCanBeUsedFixer #3981
Conversation
After mirroring 2404ec0, the NRE is gone, but the fixer isn't working. I'll debug that soon and try to figure out where things goes wrong. |
I think the async case might still be broken. I can't think of a good solution. Any ideas @mavasani @Evangelink? |
Codecov Report
@@ Coverage Diff @@
## master #3981 +/- ##
==========================================
+ Coverage 95.61% 95.77% +0.16%
==========================================
Files 1155 1165 +10
Lines 254900 264043 +9143
Branches 15272 15935 +663
==========================================
+ Hits 243724 252895 +9171
+ Misses 9215 9139 -76
- Partials 1961 2009 +48 |
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 the async case might still be broken. I can't think of a good solution.
I'm thinking of refactoring the whole fixer to use semantic analysis instead of syntax analysis, that would be ideal IMO, but very tedious.
Not sure to understand which case you are referring to, could you please add a test even if failing?
@@ -104,7 +104,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance | |||
|
|||
End Function | |||
|
|||
Private Shared Sub GetExpressionAndInvocationArguments(sourceExpression As ExpressionSyntax, isAsync As Boolean, ByRef expression As SyntaxNode, ByRef arguments As IEnumerable(Of SyntaxNode)) | |||
Private Shared Function TryGetExpressionAndInvocationArguments(sourceExpression As ExpressionSyntax, isAsync As Boolean, ByRef expression As SyntaxNode, ByRef arguments As IEnumerable(Of SyntaxNode)) As Boolean |
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 not good with vbnet and I have noticed the code was already the same before but isn't it expected to have the Out
attribute for the last 2 parameters?
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 OutAttribute is usually used for interop scenarios. But it won't be necessary here.
However, I'm not 100% sure.
@Evangelink I added the failing test for the async case |
@@ -1051,6 +1054,7 @@ End Function | |||
b = Not Await GetData().AnyAsync() |
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 remains as (Await GetData().CountAsync).Equals(IntegerZero)
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 is not the case for the parentheses scenario (Await GetData().CountAsync()).Equals(IntegerZero)
or am I missing something?
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.
@Jozkee I couldn't get what you mean
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 replying to @Evangelink's suggestion; what I got from his comment was that (Await GetData().CountAsync).Equals(IntegerZero)
(CountAsync without parentheses) should not be caught by the analyzer and I said that I disagree given that the analyzer already catches (Await GetData().CountAsync()).Equals(IntegerZero)
(CountAsync with parentheses).
We should be consistent.
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.
@Jozkee The comment from @Evangelink was before I had fixed the async case (i.e. before applying your suggestion in #3981 (comment)). He was pointing to the test failure that was expecting Not Await GetData().AnyAsync()
, but the actual was (Await GetData().CountAsync).Equals(IntegerZero)
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.
So, that's what I was missing :)
Thanks for clarifying.
expression = DirectCast(invocationExpression.Expression, MemberAccessExpressionSyntax).Expression | ||
arguments = invocationExpression.ArgumentList.ChildNodes() | ||
Dim memberAccessExpression = TryCast(sourceExpression, MemberAccessExpressionSyntax) | ||
If memberAccessExpression IsNot Nothing Then ' This case happens for something like: x = GetData().Count where Count method is called without parentheses. |
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.
There shall be a similar logic on line 126 (inside of the If isAsync Then
block)
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.
Correct, that's why Async
scenarios were not working.
Instead of duplicating the code on the async case, you can refactor the conditions to something like this:
' Extract source expression from Async expression if needed.
If isAsync Then
Dim awaitExpressionSyntax = TryCast(sourceExpression, AwaitExpressionSyntax)
If Not awaitExpressionSyntax Is Nothing Then
sourceExpression = awaitExpressionSyntax.Expression
End If
End If
' Try to get source expression as InvocationExpression.
Dim invocationExpression As InvocationExpressionSyntax = TryCast(sourceExpression, InvocationExpressionSyntax)
If invocationExpression IsNot Nothing Then
expression = DirectCast(invocationExpression.Expression, MemberAccessExpressionSyntax).Expression
arguments = invocationExpression.ArgumentList.ChildNodes()
Return True
End If
' Try to get source expression as MemberAccessExpression which is only legal on VB.
Dim memberAccessExpression = TryCast(sourceExpression, MemberAccessExpressionSyntax)
If memberAccessExpression IsNot Nothing Then
expression = memberAccessExpression.Expression
arguments = Enumerable.Empty(Of SyntaxNode)()
Return True
End If
@Jozkee for review |
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.
Thanks for the PR, could you please fix the Async scenario that is currently failing?
I left a suggestion next to @Evangelink's comment on how you can fix it.
I also left a few other comments related to to improve tests and some style suggestions.
@@ -458,6 +458,7 @@ End Function | |||
Sub M() | |||
Dim b As Boolean | |||
|
|||
b = {{|{this.Verifier.DiagnosticId}:GetData().Count.Equals(IntegerZero)|}} |
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 you add this non parentheses scenario for 0.Equals(GetData().Count)
, GetData().Count = 0
and 0 = GetData().Count
cases below?
@@ -990,6 +992,7 @@ End Function | |||
Async Sub M() | |||
Dim b As Boolean | |||
|
|||
b = {{|{this.Verifier.DiagnosticId}:(Await GetData().CountAsync).Equals(IntegerZero)|}} |
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 as my previous comment, could you please add this case on the other expressions?
|
||
Return True | ||
arguments:=arguments) Then | ||
Return True |
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.
You can Return TryGetExpressionAndInvocationArguments
instead, same for the other cases.
expression = DirectCast(invocationExpression.Expression, MemberAccessExpressionSyntax).Expression | ||
arguments = invocationExpression.ArgumentList.ChildNodes() | ||
Dim memberAccessExpression = TryCast(sourceExpression, MemberAccessExpressionSyntax) | ||
If memberAccessExpression IsNot Nothing Then ' This case happens for something like: x = GetData().Count where Count method is called without parentheses. |
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.
Move the comment to a new line on top of line 144 so its clear that this is a special scenario.
@Youssef1313 would you be able to resolve the review comments? |
@Jozkee can you please take another look? |
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.
Otherwise; lgtm, thanks.
...sualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotUseCountWhenAnyCanBeUsedFixer.vb
Outdated
Show resolved
Hide resolved
...sualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotUseCountWhenAnyCanBeUsedFixer.vb
Show resolved
Hide resolved
...sualBasic/Microsoft.NetCore.Analyzers/Performance/BasicDoNotUseCountWhenAnyCanBeUsedFixer.vb
Outdated
Show resolved
Hide resolved
…rmance/BasicDoNotUseCountWhenAnyCanBeUsedFixer.vb Co-authored-by: David Cantú <dacantu@microsoft.com>
Fixes #3749