Consider adding a distinct nullable flow state, T, to allow distinguishing "maybe null when the type allows null" from "maybe null even when type disallows null".
C#
internal enum NullableFlowState : byte
{
NotNull, // not null
T, // maybe null when type allows
MaybeNull // maybe null even when type disallows
}
FYI for @jasonmalinowski
Even if we just add the third state, without adding a corresponding type/annotation for locals, some scenarios would be improved.
Here's one reported by Sam:
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
public class C<T> {
[MaybeNull] T value;
public void M() {
if (value is null)
return;
}
}
This example just started giving me warnings in VS 16.4 Preview 2
C#
[return: MaybeNull]
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dic, TKey key, TValue defaultValue = default) where TKey : notnull =>
dic.TryGetValue(key, out var value) ? value : defaultValue;
Results in warning CS8717: A member returning a [MaybeNull] value introduces a null value when 'TValue' is a non-nullable reference type
Would this allow the [MaybeNull] from TryGetValue to flow through?
CS8717 feels subpar to me. The warning should follow the (mis)usage, not the declaration. As it stands, CS8717 only shows up on valid (even ideal) code.
void M<T>()
{
var x = new Dictionary<int, T>();
// CS8717 A member returning a [MaybeNull] value introduces a null value when 'TResult' is a
// non-nullable reference type.
// ↓
if (x.TryGetValue(42, out var someValue))
{
}
}
FYI, this is super unpleasant. I honestly don't know how to write code in some cases that is actually NRT correct. I thought MaybeNull was intended for this, but @jcouv informs me that for the actual uninstantiated cases (i.e. while you're writing generic code), you're just SOL here :-/
Most helpful comment
This example just started giving me warnings in VS 16.4 Preview 2
C# [return: MaybeNull] public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dic, TKey key, TValue defaultValue = default) where TKey : notnull => dic.TryGetValue(key, out var value) ? value : defaultValue;Results in warning CS8717: A member returning a [MaybeNull] value introduces a null value when 'TValue' is a non-nullable reference type
Would this allow the
[MaybeNull]fromTryGetValueto flow through?