Skip to content

Null coalescing in definite assignment#51567

Merged
RikkiGibson merged 12 commits intodotnet:features/improved-definite-assignmentfrom
RikkiGibson:ida-coalesce
Mar 22, 2021
Merged

Null coalescing in definite assignment#51567
RikkiGibson merged 12 commits intodotnet:features/improved-definite-assignmentfrom
RikkiGibson:ida-coalesce

Conversation

@RikkiGibson
Copy link
Member

@RikkiGibson RikkiGibson commented Mar 1, 2021

Related to #51463

This PR implements the following section of the spec:

?? (null-coalescing expressions) augment

We augment the section ?? (null coalescing) expressions as follows:

For an expression expr of the form expr_first ?? expr_second:

  • ...
  • The definite assignment state of v after expr is determined by:
    • ...
    • If expr_first directly contains a null-conditional expression E, and v is definitely assigned after the non-conditional counterpart E0, then the definite assignment state of v after expr is the same as the definite assignment state of v after expr_second.

Remarks

The above rule formalizes that for an expression like a?.M(out x) ?? (x = false), either the a?.M(out x) was fully evaluated and produced a non-null value, in which case x was assigned, or the x = false was evaluated, in which case x was also assigned. Therefore x is always assigned after this expression.

This also handles the dict?.TryGetValue(key, out var value) ?? false scenario, by observing that v is definitely assigned after dict.TryGetValue(key, out var value), and v is "definitely assigned when true" after false, and concluding that v must be "definitely assigned when true".

The more general formulation also allows us to handle some more unusual scenarios, such as:

  • if (x?.M(out y) ?? (b && z.M(out y))) y.ToString();
  • if (x?.M(out y) ?? z?.M(out y) ?? false) y.ToString();

@ghost ghost added the Area-Compilers label Mar 1, 2021
Base automatically changed from master to main March 3, 2021 23:53
@RikkiGibson RikkiGibson changed the base branch from main to features/improved-definite-assignment March 5, 2021 20:07
@RikkiGibson RikkiGibson marked this pull request as ready for review March 5, 2021 20:09
@RikkiGibson RikkiGibson requested a review from a team as a code owner March 5, 2021 20:09
@RikkiGibson
Copy link
Member Author

@jcouv @333fred @dotnet/roslyn-compiler This change is now ready for review.

int x, y;
_ = c?.M(out x, out y) ?? true
? x.ToString() // 1
: y.ToString();
Copy link
Member Author

Choose a reason for hiding this comment

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

Note that DefiniteAssignment has a behavior where once a diagnostic is reported on a variable, it will not report a diagnostic on that variable again. So in order to verify that e.g. a diagnostic is produced or not in both branches, it's more reliable to just use a different variable in each branch.

@RikkiGibson
Copy link
Member Author

@jcouv @333fred @dotnet/roslyn-compiler Please review

@RikkiGibson RikkiGibson requested review from 333fred and jcouv March 8, 2021 18:02
@jcouv jcouv self-assigned this Mar 9, 2021
@RikkiGibson
Copy link
Member Author

Please review @jcouv @333fred @dotnet/roslyn-compiler

else
{
var savedState = this.State.Clone();
var savedState = node.LeftOperand is BoundConditionalAccess
Copy link
Member

@333fred 333fred Mar 12, 2021

Choose a reason for hiding this comment

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

node.LeftOperand is BoundConditionalAccess [](start = 33, length = 42)

This doesn't feel right. What if the left side is a cast on top of a null-conditional access? #Closed

Copy link
Member Author

@RikkiGibson RikkiGibson Mar 12, 2021

Choose a reason for hiding this comment

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

There might be a need to change this to handle certain BoundConversions here, but the specification doesn't currently indicate that a ?? operand which is a cast containing a conditional access should do anything differently than today. However, it may be a good idea to change that. #Closed

Copy link
Member Author

@RikkiGibson RikkiGibson Mar 16, 2021

Choose a reason for hiding this comment

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

I was unable to find scenarios where the LHS of a ?? is a BoundConversion where:

  • there is no explicit cast
  • there are no errors on the node
  • Operand is a BoundConditionalAccess

Therefore, I put an assertion and a PROTOTYPE in the code for the moment.

When debugging the bootstrap failures I did experiment a fair bit with different scenarios to try and hit the assertion. If you have ideas for scenarios that would actually hit the assertion as written, that would be welcome.

When we start handling more kinds of containing expressions, I feel like the correct way to handle these conversion nodes is going to become more clear, so I'd like to punt it to the next PR (for ==/!=) if that is acceptable.
#Closed

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 expect to see a conversion on the left side for a scenario like this:

C c = null;
D d = c ?? default(D);

class C {}

struct D
{
    public static implicit operator D(C c) => default;
}

Make the c variable a conditionally-access method with a null, and we should be able to observe behavior.


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

@333fred
Copy link
Member

333fred commented Mar 12, 2021

Done review pass (commit 2) #Closed

@RikkiGibson
Copy link
Member Author

I believe I have addressed your comments @333fred. Please let me know if you have any more.

@RikkiGibson

This comment has been minimized.

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

@RikkiGibson
Copy link
Member Author

Changes in f4d9231 and dfcef64 are dependent on this spec change: dotnet/csharplang#4554

@333fred
Copy link
Member

333fred commented Mar 19, 2021

Done review pass (commit 9). I got through some of the tests, but some assumptions appear to be incorrect and I'll wait to look more in depth until they're addressed.

@RikkiGibson
Copy link
Member Author

Have addressed most of the feedback, but have not yet removed the tests of dubious value.

[Theory]
[InlineData("explicit")]
[InlineData("implicit")]
public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_05(string conversionKind)
Copy link
Member

Choose a reason for hiding this comment

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

Could you please add a test where the left-hand side is an NVT of an enum type?

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 9), modulo one more test suggestion.

@RikkiGibson
Copy link
Member Author

@jcouv Please let me know if you have any feedback on the changes since your last sign-off.

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

@jcouv jcouv added this to the C# 10 milestone Mar 22, 2021
@RikkiGibson RikkiGibson merged commit 91443c5 into dotnet:features/improved-definite-assignment Mar 22, 2021
@RikkiGibson RikkiGibson deleted the ida-coalesce branch March 22, 2021 23:38
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