`string s = null` was meant to be `string s = string.Empty.
4.2 KiB
C# Language Design for February 13th, 2019
Agenda
Nullable Reference Types: Open LDM Issues https://github.com/dotnet/csharplang/issues/2201
Discussion
Track assignments through ref
with conditional expressions
What is the nullability of ref
variables when assigned through conditional expressions?
string? x = "";
string? y = "";
(b ? ref x : ref y) = null;
x.ToString(); // warning?
y.ToString(); // warning?
string? x = null;
string? y = null;
(b ? ref x : ref y) = "";
One option is to assume nullability after a ref
has been taken to a variable. However,
that would mean that a ref
variable declared non-nullable could become nullable, which
seems too heavy-handed.
Similarly, disabling flow analysis for variables which are taken as the target of a ref
feels like violating our model, which is largely based on flow analysis.
Alias analysis, on the other hand, seems to complicated and any reliable implementation would be too difficult for users to understand.
Conclusion
Let's reach a middle ground. Assignment between any two identifiers copies
the state and is then tracked separately. This is also true for ref
locals.
So,
string? x = "";
string? y = "";
(b ? ref x : ref y) = null;
x.ToString(); // warning
y.ToString(); // warning
But the equivalent using ref
locals does not.
string? x = "";
string? y = "";
if (b)
{
ref string? rx = ref x;
rx = null;
}
else
{
ref string? ry = ref y;
ry = null;
}
x.ToString(); // no warning
y.ToString(); // no warning
Nullability of conditional access with unconstrained type parameters
What is the nullability of x?.F()
?
class C<T, U>
where T : U
where U : C<T, U>?
{
static void M(U x)
{
U y = x?.F();
T z = x?.F();
}
T F() => throw null;
}
This question seems interesting even without the type parameters and also contains a nested question about reachability.
Is the null
case of ?.
reachable even if the expression is non-nullable?
And if it is, what is the null state of the LHS?
string x = "";
x?.ToString(); // warning?
Conclusion
The null
case of ?.
is always reachable, meaning the result is always
maybe null e.g.,
var y = x?.M(); // y is maybe null here, if possible
Moreover, the LHS is considered maybe null in the null branch, so by normal flow analysis, after the expression is evaluated a variable on the LHS will be considered maybe null.
string x = "";
x?.ToString(); // warning that x is maybe null
!
operator on L-values
Where is !
allowed?
-
M(out x!);
(note this also definitely assigns tox
through the!
) -
M(out (x!));
-
M(out (RefReturning()!));
-
x! = y;
-
M(out string x!);
Current implementation is to allow in out
scenarios, but disallow in assignment scenarios.
We dislike allowing it in regular assignment. We like allowing it in simple
out
parameters. We're ambivalent on M(out string x!)
, but it's
not easily representable in the syntax model and is very similar to the
related feature parameter!
, with the opposite meaning.
Conclusion
Only allow !
in simple out
parameters with no declaration.
is
nullability in false
case
string s = string.Empty;
if (!(s is object)) { s.ToString(); /* could warn? */ }
if (!(s is string)) { s.ToString(); /* could warn? */ }
There are variants of this scenario with string!
and string~
. Should is
update the nullability in both branches or should the one branch be treated
as unreachable?
The problematic code is probably more like:
void M(string s)
{
if (s is IComparable t)
{
}
s.ToString(); // warning
}
Here the user may not have meant to do a null check, but gets the side-effects of doing so anyway.
Conclusion
It seems important to respect a deliberate null check from the
user, even if the input variable is non-nullable. As a next step we
need to define exactly which tests we think are "deliberate" null
checks. For instance, x is null
is certainly a null check, but
pattern matching may or may not be a deliberate null check, even
if the code contains a null check.