Skip to content
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

Merged
merged 19 commits into from
Jun 28, 2021

Conversation

Youssef1313
Copy link
Member

@Youssef1313 Youssef1313 commented Jun 8, 2021

Fixes #53861

@CyrusNajmabadi Anything for VB here?

@@ -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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Should we match semantics?

Suggested change
<value>Prefer 'is null' check over 'is object'</value>
<value>Prefer 'is not null' check over 'is object'</value>

Copy link
Member Author

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).

Copy link
Member

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 :)

Copy link
Member Author

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?

Copy link
Member

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".

Copy link
Member

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.

Copy link
Member Author

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.

var severity = option.Notification.Severity;
context.ReportDiagnostic(
DiagnosticHelper.Create(
Descriptor, context.Operation.Syntax.GetLocation(), severity, additionalLocations: null, properties: null));
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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 :-)

Copy link
Member Author

@Youssef1313 Youssef1313 Jun 8, 2021

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)

Copy link
Member

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.

@Youssef1313 Youssef1313 changed the title Implement 'Prefer is null over is object' Implement 'Prefer null check over type check' Jun 8, 2021
Comment on lines 53 to 68
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());
}
Copy link
Contributor

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):

Suggested change
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()),
};

Comment on lines 77 to 78
return negatedPattern.Pattern is ITypePatternOperation typePatternOperation &&
typePatternOperation.InputType.InheritsFromOrEquals(typePatternOperation.MatchedType);
Copy link
Contributor

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?

Copy link
Member Author

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|];
Copy link
Contributor

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";
  }

@Youssef1313 Youssef1313 marked this pull request as ready for review June 20, 2021 18:07
@Youssef1313 Youssef1313 requested a review from a team as a code owner June 20, 2021 18:07
@Youssef1313
Copy link
Member Author

@CyrusNajmabadi This is ready for another look

@Youssef1313
Copy link
Member Author

D:\workspace\_work\1\s\src\NuGet\Microsoft.Net.Compilers.Toolset\Microsoft.Net.Compilers.Toolset.Package.csproj : error NU3037: Package 'System.Buffers 4.4.0' from source 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json': The repository primary signature validity period has expired.
D:\workspace\_work\1\s\src\NuGet\Microsoft.Net.Compilers.Toolset\Microsoft.Net.Compilers.Toolset.Package.csproj : error NU3037: Package 'System.Numerics.Vectors 4.4.0' from source 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json': The repository primary signature validity period has expired.
D:\workspace\_work\1\s\.tools\msbuild\16.8.0-preview2.1\tools\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGet.RestoreEx.targets(10,5): error : Unable to delete temporary file 'C:\Users\vsagent\.nuget\packages\system.memory\4.5.3\ov03uxuv.lky'. Error: 'Could not find a part of the path 'C:\Users\vsagent\.nuget\packages\system.memory'.'. [D:\workspace\_work\1\s\src\NuGet\Microsoft.Net.Compilers.Toolset\Microsoft.Net.Compilers.Toolset.Package.csproj]
    0 Warning(s)
    3 Error(s)

Closing and reopening for new build.

@Youssef1313 Youssef1313 reopened this Jun 21, 2021
@Youssef1313
Copy link
Member Author

@CyrusNajmabadi I addressed your feedback

}

context.RegisterOperationAction(c => AnalyzeIsTypeOperation(c), OperationKind.IsType);
context.RegisterOperationAction(c => AnalyzeNegatedPatternOperation(c), OperationKind.NegatedPattern);
Copy link
Member

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;
Copy link
Member

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.

expression: binary.Left,
pattern: UnaryPattern(ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression)))),
UnaryPatternSyntax =>
ConstantPattern(LiteralExpression(SyntaxKind.NullLiteralExpression)),
Copy link
Member

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>
Copy link
Member

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?

Copy link
Member Author

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. :(

@jinujoseph jinujoseph added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Jun 23, 2021
@CyrusNajmabadi CyrusNajmabadi merged commit 48dd54f into dotnet:main Jun 28, 2021
@ghost ghost added this to the Next milestone Jun 28, 2021
@CyrusNajmabadi
Copy link
Member

thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE Community The pull request was submitted by a contributor who is not a Microsoft employee. Feature Request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Suggest replacing is object with the equivalent is not null for clarity
6 participants