-
Couldn't load subscription status.
- Fork 1.1k
Description
Improved Definite Assignment Analysis
Summary
Definite assignment analysis as specified has a few gaps which have caused users inconvenience. In particular, scenarios involving comparison to boolean constants, conditional-access, and null coalescing.
Related discussions and issues
csharplang discussion of this proposal: #4240
Probably a dozen or so user reports can be found via this or similar queries (i.e. search for "definite assignment" instead of "CS0165", or search in csharplang).
https://github.com/dotnet/roslyn/issues?q=is%3Aclosed+is%3Aissue+label%3A%22Resolution-By+Design%22+cs0165
I have included related issues in the scenarios below to give a sense of the relative impact of each scenario.
Scenarios
As a point of reference, let's start with a well-known "happy case" that does work in definite assignment and in nullable.
#nullable enable
C c = new C();
if (c != null && c.M(out object obj0))
{
obj0.ToString(); // ok
}
public class C
{
public bool M(out object obj)
{
obj = new object();
return true;
}
}Comparison to bool constant
- Proposal: Definite assignment involving == and != when compared to a constant boolean value #801
- [Question] Why the output is CS0165 when using a is Constant pattern. roslyn#45582
- Links to 4 other issues where people were affected by this.
if ((c != null && c.M(out object obj1)) == true)
{
obj1.ToString(); // undesired error
}
if ((c != null && c.M(out object obj2)) is true)
{
obj2.ToString(); // undesired error
}Comparison between a conditional access and a constant value
- Null-safe navigation and null-coalescing break the out variable initialization detection roslyn#33559
- Use of null propagation and out variables #4214
- Conditional operators and definite assignment #3659
- Compiler Prediction Error #3485
- Conditional operators and definite assignment #3659
This scenario is probably the biggest one. We do support this in nullable but not in definite assignment.
if (c?.M(out object obj3) == true)
{
obj3.ToString(); // undesired error
}Conditional access coalesced to a bool constant
- Extend definite assignment to understand null and nullable values and ?. #916
- Improve definite assignment to understand coalescing with false and nullable comparison with true #3365
This scenario is very similar to the previous one. This is also supported in nullable but not in definite assignment.
if (c?.M(out object obj4) ?? false)
{
obj4.ToString(); // undesired error
}Conditional expressions where one arm is a bool constant
It's worth pointing out that we already have special behavior for when the condition expression is constant (i.e. true ? a : b). We just unconditionally visit the arm indicated by the constant condition and ignore the other arm.
Also note that we haven't handled this scenario in nullable.
if (c != null ? c.M(out object obj4) : false)
{
obj4.ToString(); // undesired error
}Specification
The specification has moved to https://github.com/dotnet/csharplang/blob/master/proposals/improved-definite-assignment.md
Drawbacks
It feels odd to have the analysis "reach down" and have special recognition of conditional accesses, when typically flow analysis state is supposed to propagate upward. We are concerned about how a solution like this could intersect painfully with possible future language features that do null checks.
Alternatives
Two alternatives to this proposal:
- Introduce "state when null" and "state when not null" to the language and compiler. This has been judged to be too much effort for the scenarios we are trying to solve, but that we could potentially implement the above proposal and then move to a "state when null/not null" model later on without breaking people.
- Do nothing.
Unresolved questions
-
There are impacts on switch expressions that should be specified: Improved Definite Assignment Analysis #4240 (reply in thread)
-
It could be useful to also allow certain expressions using lifted relational operators to benefit: Learn from bool constants and conditional accesses inside ==/!= roslyn#52425 (comment)
public class C {
public int M0(object obj) => 0;
public static void M1(C? c) {
int x, y;
_ = c?.M0(x = y = 0) <= 0
? x.ToString() // no warning
: y.ToString(); // warning
}
}