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

Improved nullable '??' analysis #52646

Conversation

RikkiGibson
Copy link
Contributor

@RikkiGibson RikkiGibson commented Apr 15, 2021

Test plan: #51463

Implements "improved" nullable analysis of null coalescing '??' operators. It may be useful to reference #51567 when reviewing this.

@RikkiGibson RikkiGibson changed the base branch from main to features/improved-definite-assignment April 15, 2021 01:38
@RikkiGibson RikkiGibson marked this pull request as ready for review April 20, 2021 19:00
@RikkiGibson RikkiGibson requested a review from a team as a code owner April 20, 2021 19:00
@RikkiGibson RikkiGibson marked this pull request as draft April 20, 2021 22:47
@RikkiGibson RikkiGibson marked this pull request as ready for review April 20, 2021 22:47
@RikkiGibson
Copy link
Contributor Author

Please take a look @jcouv @333fred @dotnet/roslyn-compiler.

@jcouv jcouv self-assigned this Apr 22, 2021
@RikkiGibson
Copy link
Contributor Author

Please take a look @jcouv @333fred @dotnet/roslyn-compiler.


if (access is object)
{
EnterRegionIfNeeded(access);
Copy link
Member

Choose a reason for hiding this comment

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

Do the Enter/LeaveRegionIfNeeded calls matter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When doing the definite assignment side of this feature, I had to add these calls in the analogous position over there, because the usage of VisitConditionalAccess(node, out stateWhenNotNull) caused us to side-step the regular machinery for managing region analysis in AbstractFlowPass.VisitAlways. When this was missing in definite assignment, "extract method" and similar feature tests were broken.

I do not actually know what dependent features could be broken by the absence of this in NullableWalker, or how to test it in a more direct manner, but it seems like something that we should have anyway.

Copy link
Member

Choose a reason for hiding this comment

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

Other nullable analysis methods don't have this, except that we set a value once in Scan (not exactly sure what that achieves either). I think we should remove it unless a scenario warrants it.


In reply to: 620688053 [](ancestors = 620688053)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@333fred
Copy link
Member

333fred commented Apr 26, 2021

Done review pass (commit 1)

static void M2(bool? b)
{
var obj = new object();
_ = b?.M0(obj = null)?.M0(obj = new object()) ?? false
Copy link
Member

Choose a reason for hiding this comment

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

Nice :-)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

Done with review pass (iteration 1)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 4)

@jcouv jcouv added this to the C# 10 milestone Apr 27, 2021
@RikkiGibson
Copy link
Contributor Author

I believe I've addressed all your feedback @333fred, please let me know if you have any more.


void M1(C? c1, object? x)
{
S s = (S?)c1?.M2(x = 0) ?? c1!.Value.M3(x = 0); // 1
Copy link
Member

Choose a reason for hiding this comment

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

Consider using parens to make it clear where the cast is applying.

{
static void M1(C c1, object? x)
{
B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); // 1
Copy link
Member

@333fred 333fred Apr 29, 2021

Choose a reason for hiding this comment

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

This one feels a bit wonky to me. My initial reaction is that it should warn for this case. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do warn for this case--just on the conversion instead of on usage of the updated variables. :)

The principle behind the behavior you're seeing is that we can propagate out "state when not null" from a user-defined conversion unless it can return non-null output for a null input. The rule I implemented based on this is that we can propagate out "state when not null" under the following conditions:

  1. either the parameter is non-nullable, or
  2. the return is nullable and has a [return: NotNullIfNotNull] referencing the parameter.

The first rule means in practice that the user-defined conversion needs to have a non-nullable value type parameter and be lifted. Otherwise we either gave a warning on the conversion or there was a suppression on the operand.

Alternatively, we could adjust the rule to say we can propagate out "state when not null" when:

  1. the parameter is of a non-nullable value type, or
  2. the return is nullable and has a [return: NotNullIfNotNull] referencing the parameter.

I'm not extremely attached to it going either way.

@RikkiGibson
Copy link
Contributor Author

I decided to use the same rules for "acceptable conversions" in both definite assignment and nullable for the time being. The more I thought about it, the more niche/contrived it felt to use a user-defined conversion on something and then null test it. It's easier to "loosen up" and remove warnings down the line, so I think it's OK to remain strict for now.

}
";
CreateNullableCompilation(new[] { source, NotNullIfNotNullAttributeDefinition }).VerifyDiagnostics(
// (13,9): warning CS8602: Dereference of a possibly null reference.
Copy link
Member

Choose a reason for hiding this comment

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

I think this a perfect example of the compromise of stricter methods here, and if we want to solve it for user-defined conversions, the equivalent static method should be solved as well :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Related to #36164

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

LGTM (commit 7). One minor refactoring suggestion.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 7)

@RikkiGibson RikkiGibson merged commit 884d730 into dotnet:features/improved-definite-assignment May 4, 2021
@RikkiGibson RikkiGibson deleted the ida-nullable-coalesce branch May 4, 2021 01:12
@RikkiGibson RikkiGibson restored the ida-nullable-coalesce branch May 4, 2021 01:12
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.

3 participants