Update:
A test like c?.Count > 0 should tell us that c is not-null in the when-true branch.
```C#
public class GreenNode
{
internal GreenNode? GetFirstTerminal()
{
GreenNode? node = this;
do
{
GreenNode? firstChild = null;
for (int i = 0, n = 1; i < n; i++)
{
var child = node.GetSlot(i); // warning CS8602: Possible dereference of a null reference.
if (child != null)
{
firstChild = child;
break;
}
}
node = firstChild;
} while (node?._slotCount > 0); // this check guarantees that node isn't null in the second iteration
return node;
}
internal GreenNode? GetSlot(int i) => throw null;
internal int _slotCount = 0;
}
```
Found in nullable dogfood.
Tagging @cston
:bulb: it took me a bit to realize what happened when I reviewed this on a small screen. It would help to also comment the line that ensures node isn't null on the first iteration.
This looks like a variant of #25870.
The problem still seems to be there after the null-conditional access has been fixed.
class C
{
void M()
{
C? c = this;
do
{
c.ToString(); // warning CS8602: Possible dereference of a null reference.
c = c.Next; // warning CS8602: Possible dereference of a null reference.
}
while (c?.Count > 0);
}
int Count => 0;
C? Next => null;
}
The issue may be comparing with something other than null. The following results in warning for the > 0 case:
c#
static void M(string? s)
{
if (s?.Length != null) s.ToString();
if (s?.Length > 0) s.ToString(); // warning
}
Tagging @333fred FYI
Yes, the propagation only runs as part of comparisons to null. We need to change that.
Closing as duplicate of https://github.com/dotnet/roslyn/issues/31906 (Infer nullability of receiver from ?. expression compared with non-null).
For example, s?.Length == 1 tells us that s is not-null in the when-true branch.