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

Fix S2219 FP: "Use the is operator" reports "unfixable" code #6675

Merged
merged 4 commits into from
Feb 2, 2023

Conversation

antonioaversa
Copy link
Contributor

@antonioaversa antonioaversa commented Jan 26, 2023

Fixes #6616

Made into a draft because it should be merged after the release.

Remark:
As stated in the reporting of #6616, conditionals like typeof(ISet<>).IsInstanceOfType(obj) are guaranteed to always return false for any obj.
While this PR disables the rule (and related quick fix) in such scenarios, another approach could be to report that the check is always false.
This seems, however, a different rule, since it doesn't involve the is operator, and would not offer a quickfix.

@antonioaversa antonioaversa force-pushed the Antonio/S2219-fp-is-operator-unfixable-code branch from 9d626ae to f172b9b Compare January 26, 2023 14:11
@antonioaversa antonioaversa marked this pull request as ready for review January 26, 2023 14:24
@antonioaversa antonioaversa changed the title No report with unbounded generic types in InstanceOfType and IsAssignableFrom Fix S2219 FP: "Use the is operator" reports "unfixable" code Jan 26, 2023
@antonioaversa antonioaversa marked this pull request as draft January 27, 2023 09:29
Copy link
Contributor

@pavel-mikula-sonarsource pavel-mikula-sonarsource left a comment

Choose a reason for hiding this comment

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

Let's try to do this on syntax level

Comment on lines 100 to 103
_ = typeof(ISet<int>).IsInstanceOfType(obj); // Noncompliant, bounded generic type
_ = typeof(ISet<>).IsInstanceOfType(obj); // Compliant, unbonded generic type
_ = typeof(IDictionary<int, int>).IsInstanceOfType(obj); // Noncompliant, bounded generic type
_ = typeof(IDictionary<,>).IsInstanceOfType(obj); // Compliant, unbonded generic type
Copy link
Contributor

Choose a reason for hiding this comment

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

These comments would be way more readable when aligned on the same tab ditance

Copy link
Contributor

Choose a reason for hiding this comment

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

Same below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have tried to line those comments up, but I bumped into the following issue by the code fix test GetTypeWithIsAssignableFrom_CodeFix: Expected ActualCodeWithReplacedComments().ToUnixLineEndings() to be X with a length of 5756 because VerifyWhileDocumentChanges updates the document until all issues are fixed, even if the fix itself creates a new issue again. Language: CSharp7, but Y has a length of 5819, differs near " " (index 3683), where the only differences are in whitespaces:

image

I think that the test just preserves whitespaces when replacing // Noncompliant with // Fixed on a per-line basis, so lines end up misaligned in GetTypeWithIsAssignableFrom.Fixed.cs and GetTypeWithIsAssignableFrom.Fixed.Batch.cs (because fixes are of different length) and comparisons are sensitive to the difference in the number of whitespaces.

So we basically:

  1. either have comments lined up in the main test-case file, and scrambled in Fixed.cs and Fixed.Batch.cs;
  2. or have comments starting 1 chars from the ; (end of instruction), on the three files.

While not optimal, I think option 2 is more consistent and makes Fixed.cs and Fixed.Batch.cs easier to edit.

If you still prefer option 1, I can make the change.

Copy link
Contributor

Choose a reason for hiding this comment

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

Having the // Fixed stuff misaligned is (not nice but) acceptable. We'll eventually improve the test framework to ignore the whitespace differences for these comments (for other comments, it might be significant)

So let's go for 1.

Copy link
Contributor

Choose a reason for hiding this comment

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

Basically the reasoning is that now, it's not readable anywhere. After, it will be readable at least on the main source. While we don't care too much about the comments of the fixed part.

@antonioaversa antonioaversa modified the milestone: 8.53 Jan 31, 2023
@antonioaversa antonioaversa marked this pull request as ready for review January 31, 2023 16:49
Copy link
Contributor

@pavel-mikula-sonarsource pavel-mikula-sonarsource left a comment

Choose a reason for hiding this comment

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

LGTM. There's whitespace and one more UT to be added before merging

@@ -136,6 +141,9 @@ private static void CheckForIsAssignableFrom(SonarSyntaxNodeReportingContext con
}
}

private static bool IsUnboundedGenericType(TypeOfExpressionSyntax typeOf) =>
typeOf.DescendantNodes().Any(x => x is OmittedTypeArgumentSyntax);
Copy link
Contributor

Choose a reason for hiding this comment

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

DescendantNodes() is typically an expensive operation. Especially on a method level. Inside a typeof, it's fine, as we're sure that it will not contain a large sub-tree. So the implementation is good here.

The question is then, what about nested generic types? Can you have IDictionary<string, ISet<>>? Let's add UT

Copy link
Contributor Author

@antonioaversa antonioaversa Feb 1, 2023

Choose a reason for hiding this comment

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

Tests added.

I've found a property of GenericNameSyntax called IsUnboundGenericName:

public bool IsUnboundGenericName
{
    get
    {
        return this.TypeArgumentList.Arguments.Any(SyntaxKind.OmittedTypeArgument);
    }
}

c.p. https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Syntax/GenericNameSyntax.cs#L19

The method above has a very similar implementation to the one we have drafted together. I used that method instead.

One additional note: the actual check is slightly more complex since a QualifiedNameSyntax does not derive from GenericNameSyntax, but contains it as Right member for namespaced type:

typeof(System.Collections.Generic.ISet<>)
image

typeof(ISet<>)
image

@sonarcloud
Copy link

sonarcloud bot commented Feb 1, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

100.0% 100.0% Coverage
0.0% 0.0% Duplication

@sonarcloud
Copy link

sonarcloud bot commented Feb 1, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@antonioaversa antonioaversa merged commit 738e787 into master Feb 2, 2023
@antonioaversa antonioaversa deleted the Antonio/S2219-fp-is-operator-unfixable-code branch February 2, 2023 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix S2219 FP: "Use the is operator" reports "unfixable" code
2 participants