# 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<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?

```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.
