# 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? ```cs string? x = ""; string? y = ""; (b ? ref x : ref y) = null; x.ToString(); // warning? y.ToString(); // warning? ``` ```cs 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, ```C# string? x = ""; string? y = ""; (b ? ref x : ref y) = null; x.ToString(); // warning y.ToString(); // warning ``` But the equivalent using `ref` locals does not. ```C# 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()`? ```cs class C where T : U where U : C? { 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? ```C# string x = ""; x?.ToString(); // warning? ``` **Conclusion** The `null` case of `?.` is always reachable, meaning the result is always maybe null e.g., ```C# 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. ```C# 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 to `x` 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 See [dotnet/roslyn#30297](https://github.com/dotnet/roslyn/issues/30297) ```cs 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: ```C# 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.