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

Parameter null-checking analyzer and fixer #58182

Merged
merged 16 commits into from
Jan 12, 2022
Merged

Conversation

RikkiGibson
Copy link
Contributor

Related to #36024

@RikkiGibson
Copy link
Contributor Author

RikkiGibson commented Jan 7, 2022

The fix all in solution results for Roslyn Compilers.sln is an interesting read. 😊

@CyrusNajmabadi
Copy link
Member

The way to fix this issue is to preprocess the full list of diagnostics you are fixing. So you'll group them by parameter and then fix the parameter once and the N associated statements at the same time.

@RikkiGibson
Copy link
Contributor Author

It seems like the current approach of gathering and checking parameter locations in a set is adequate and doesn't require a separate initial pass over the diagnostics.

Copy link
Member

@Youssef1313 Youssef1313 left a comment

Choose a reason for hiding this comment

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

Worth adding a test like this?

using System;

class C<T> where T : struct
{
   public void M(T? s!!) { }
}

@RikkiGibson
Copy link
Contributor Author

See TestOnStructConstrainedTypeParameter

@RikkiGibson
Copy link
Contributor Author

@CyrusNajmabadi I believe I have addressed all your comments. Please let me know if you have any others.

@CyrusNajmabadi
Copy link
Member

Looking now :)

return;
}

var argumentNullException = compilation.GetTypeByMetadataName(ArgumentNullExceptionName);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
var argumentNullException = compilation.GetTypeByMetadataName(ArgumentNullExceptionName);
var argumentNullException = compilation.GetBestTypeByMetadataName(ArgumentNullExceptionName);

if (parameter is not null
&& (!parameter.Type.IsValueType
|| parameter.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T
|| parameter.Type.TypeKind is TypeKind.Pointer or TypeKind.FunctionPointer)
Copy link
Member

Choose a reason for hiding this comment

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

extract into helper plz (can be local function or method). mixing the && || and ! is too much for my brain :)

// if (param is null) { throw new ArgumentNullException(nameof(param)); }
// if (object.ReferenceEquals(param, null)) { throw new ArgumentNullException(nameof(param)); }
case IfStatementSyntax ifStatement:
ExpressionSyntax left, right;
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 extract the entire if-handling code to it's own helper.

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 think I won't make this change due to lack of uniformity with the ExpressionStatement case. See #58182 (comment)

return parameterInBinary;

// this.field = param ?? throw new ArgumentNullException(nameof(param));
case ExpressionStatementSyntax
Copy link
Member

Choose a reason for hiding this comment

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

similarly, can you extract this case out to a helper?

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 found that since much of the complexity was in the pattern itself, the extracted logic didn't feel sufficiently complex to justify a single-use helper.

return false;
}

return argumentNullExceptionStringConstructor.Equals(semanticModel.GetSymbolInfo(exceptionCreation, cancellationToken).Symbol);
Copy link
Member

Choose a reason for hiding this comment

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

consider reordering. it's a personal nit, but it seems more sensible to first see if you're doing new ArgumntNullException then check the argument.

{
using VerifyCS = CSharpCodeFixVerifier<CSharpUseParameterNullCheckingDiagnosticAnalyzer, CSharpUseParameterNullCheckingCodeFixProvider>;

public class UseParameterNullCheckingTests
Copy link
Member

Choose a reason for hiding this comment

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

are there tests for records?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you referring to record primary constructors? Currently I don't think there's a way to get this particular code fix to activate on primary constructors. The "add null check" refactoring should work for it, but it doesn't. Opened #58779

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseIsNullCheck)]
public async Task TestNotEqualitySwapped()
{
var testCode = @"using System;
Copy link
Member

Choose a reason for hiding this comment

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

possibly comment that this is just because we don't support this scenario yet.

}
},
LanguageVersion = LanguageVersionExtensions.CSharpNext
}.RunAsync();
Copy link
Member

Choose a reason for hiding this comment

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

add test for:

public C(
      string x // comment
    , string y // comment,
    , string z // comment
    )
{
}

Copy link
Contributor Author

@RikkiGibson RikkiGibson Jan 11, 2022

Choose a reason for hiding this comment

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

Good test case! We actually put the !! on the next line here. It feels like the trailing trivia of the identifier should move over to the !!. Is there an existing helper that lets me "insert a token and move over another token's trivia" in this way?

edit: added some additional tests and just did it the way that seemed reasonable to me.

Copy link
Member

Choose a reason for hiding this comment

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

nope. you'd have to write the logic yourself. note: i'm also ok with you leaving that out of this PR and waiting until tehre is user feedback :)

@@ -166,6 +166,7 @@ internal static class IDEDiagnosticIds
public const string SimplifyPropertyPatternDiagnosticId = "IDE0170";

public const string UseTupleSwapDiagnosticId = "IDE0180";
public const string UseParameterNullCheckingId = "IDE0190";
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public const string UseParameterNullCheckingId = "IDE0190";
public const string UseParameterNullCheckingId = "IDE0190";

@RikkiGibson RikkiGibson merged commit 7c60002 into dotnet:main Jan 12, 2022
@RikkiGibson RikkiGibson deleted the pnc-fixer branch January 12, 2022 00:58
@ghost ghost added this to the Next milestone Jan 12, 2022
@RikkiGibson RikkiGibson modified the milestones: Next, 17.2.P1 Feb 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants