-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Implement 'Prefer null check over type check' #53947
Conversation
@@ -437,4 +437,7 @@ | |||
<data name="Outside_namespace" xml:space="preserve"> | |||
<value>Outside namespace</value> | |||
</data> | |||
<data name="Prefer_is_null_check_over_is_object" xml:space="preserve"> | |||
<value>Prefer 'is null' check over 'is object'</value> |
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 we match semantics?
<value>Prefer 'is null' check over 'is object'</value> | |
<value>Prefer 'is not null' check over 'is object'</value> |
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 I wasn't able to word it accurately in a small sentence. It's both ways (prefer is null
over is not object
and prefer is not null
over is object
).
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 would have both forms :)
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.
Is something like Prefer 'is null'/'is not null' checks over 'is not object'/'is object'
good enough?
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 don't love it. But I could live with it. @sharwell ?
My pref really would be a unique string for each, or something like "Prefer 'null' check over type check".
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 still like my suggestion here.
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 thought I already did that (using unique string for each), but turns out this resource file is for editorconfig UI, and what I did was for the analyzer. Actually, a unique string cannot be done in EditorConfig UI.
I'll use "Prefer 'null' check over type check" for both the analyzer and EditorConfig UI.
src/Analyzers/CSharp/Tests/UseIsNullCheck/UseIsNullCheckOverIsObjectTests.cs
Outdated
Show resolved
Hide resolved
src/Analyzers/CSharp/Analyzers/UseIsNullCheck/CSharpUseIsNullCheckOverIsObject.cs
Outdated
Show resolved
Hide resolved
src/Analyzers/CSharp/Analyzers/UseIsNullCheck/CSharpUseIsNullCheckOverIsObject.cs
Outdated
Show resolved
Hide resolved
src/Analyzers/CSharp/Analyzers/UseIsNullCheck/CSharpUseIsNullCheckOverIsObject.cs
Outdated
Show resolved
Hide resolved
var severity = option.Notification.Severity; | ||
context.ReportDiagnostic( | ||
DiagnosticHelper.Create( | ||
Descriptor, context.Operation.Syntax.GetLocation(), severity, additionalLocations: null, properties: 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.
i'd like for us to ensure the syntax is what we expect as well. in case this pattern was somehow emitted in the bound nodes but on syntax we don't expect.
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.
@CyrusNajmabadi I'm checking the syntax in the codefix and throwing ExceptionUtilities.UnexpectedValue
on unexpected syntax.
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.
Definitely not the way to do it :-)
Analyzers should not report issues if the fix would crash on them :-)
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.
@CyrusNajmabadi Going to move the check in the analyzer instead. Should I keep throwing or just bail-out on unexpected nodes?
Or perhaps keep the check in the codefix but return silently without crashing instead? (one benefit to this is we can get feedback from users when the codefix doesn't offer anything so we can see what syntax causes that, whether it could be handled, etc)
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 sound cleanly bail out in release mode. We can consider a debug assert for debug mode, but my preference is not too have that.
We can effectively drive completeness in these cases through dogfooding and user reports.
src/Analyzers/CSharp/Analyzers/UseIsNullCheck/CSharpUseIsNullCheckOverIsObject.cs
Outdated
Show resolved
Hide resolved
src/Analyzers/CSharp/Tests/UseIsNullCheck/UseIsNullCheckOverIsObjectTests.cs
Outdated
Show resolved
Hide resolved
SyntaxNode? replacement; | ||
// Replace 'x is object' with 'x is not null' | ||
if (node is BinaryExpressionSyntax binary) | ||
{ | ||
replacement = SyntaxFactory.IsPatternExpression( | ||
expression: binary.Left, | ||
pattern: SyntaxFactory.UnaryPattern(SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))); | ||
} | ||
else if (node is UnaryPatternSyntax) | ||
{ | ||
replacement = SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)); | ||
} | ||
else | ||
{ | ||
throw ExceptionUtilities.UnexpectedValue(node.Kind()); | ||
} |
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.
Consider using using static Microsoft.CodeAnalysis.CSharp.Syntax.SyntaxFactory;
and a switch expression instead (Code not tested):
SyntaxNode? replacement; | |
// Replace 'x is object' with 'x is not null' | |
if (node is BinaryExpressionSyntax binary) | |
{ | |
replacement = SyntaxFactory.IsPatternExpression( | |
expression: binary.Left, | |
pattern: SyntaxFactory.UnaryPattern(SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)))); | |
} | |
else if (node is UnaryPatternSyntax) | |
{ | |
replacement = SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)); | |
} | |
else | |
{ | |
throw ExceptionUtilities.UnexpectedValue(node.Kind()); | |
} | |
var replacement = node switch | |
{ | |
// Replace 'x is object' with 'x is not null' | |
BinaryExpressionSyntax binary => | |
IsPatternExpression( | |
expression: binary.Left, | |
pattern: UnaryPattern(ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression)))), | |
UnaryPatternSyntax => | |
ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression)), | |
_ => throw ExceptionUtilities.UnexpectedValue(node.Kind()), | |
}; |
return negatedPattern.Pattern is ITypePatternOperation typePatternOperation && | ||
typePatternOperation.InputType.InheritsFromOrEquals(typePatternOperation.MatchedType); |
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.
Shouldn't there be a check, that there is no designation present like in is not object o
?
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.
@MaStr11 negatedPattern.Pattern
won't be ITypePatternOperation for the is not object o
case. Going to add a test.
{ | ||
public bool M(string value) | ||
{ | ||
return value is [|not string|]; |
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.
Add test for something like:
if (value is not string s)
{
}
else
{
_ = s == "SomeValue";
}
@CyrusNajmabadi This is ready for another look |
Closing and reopening for new build. |
@CyrusNajmabadi I addressed your feedback |
} | ||
|
||
context.RegisterOperationAction(c => AnalyzeIsTypeOperation(c), OperationKind.IsType); | ||
context.RegisterOperationAction(c => AnalyzeNegatedPatternOperation(c), OperationKind.NegatedPattern); |
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.
👍
} | ||
|
||
severity = option.Notification.Severity; | ||
return context.Operation.Syntax is BinaryExpressionSyntax or UnaryPatternSyntax; |
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.
consider removing this and moving the individual checks into the respective ANalyzeXXX helpers.
...alyzers/CSharp/Analyzers/UseIsNullCheck/CSharpUseNullCheckOverTypeCheckDiagnosticAnalyzer.cs
Show resolved
Hide resolved
...alyzers/CSharp/Analyzers/UseIsNullCheck/CSharpUseNullCheckOverTypeCheckDiagnosticAnalyzer.cs
Show resolved
Hide resolved
expression: binary.Left, | ||
pattern: UnaryPattern(ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression)))), | ||
UnaryPatternSyntax => | ||
ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression)), |
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.
consider pulling ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression))
out (maybe into a satatic) and just referncing here. But feel free not to :)
@@ -640,4 +640,7 @@ | |||
<data name="Automatically_complete_statement_on_semicolon" xml:space="preserve"> | |||
<value>Automatically complete statement on semicolon</value> | |||
</data> | |||
<data name="Prefer_null_check_over_type_check" xml:space="preserve"> | |||
<value>Prefer null check over type check</value> |
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.
this is inconsistent 'null'
should be in quotes. Also... this feels like a duplicated string. Can we unify?
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 duplication is tracked by #52343. This is not for this analyzer only. :(
thanks! |
Fixes #53861
@CyrusNajmabadi Anything for VB here?