if (s != null) WriteLine(s.Length); // How is the warning silenced?
if (t != null) WriteLine(t.ToString()); // How is the warning silenced?
}
```
So far we've said that when a nullable variable is known to not be null, it's *value* is simply considered to be of the underlying nonnullable type. So for `s` in the above example, inside the `if` where it is tested, its value is of type `string`. Thus, the dereference does not earn a warning.
However, this doesn't immediately work for type parameters. In the example above, we don't *know* that `T` is a nullable reference type, we just have to assume it. Therefore, it does not *have* an underlying nonnullable type. We could invent one, say `T!`, that means "nonnull `T` if `T` was nullable", but it starts to get complex.
An alternative mechanism is to say that the type of a variable and its value *does not change*. The null state tracking does not work through changing the type, but simply by directly silencing null warnings on "dangerous" operations.
With the new proposal, you can better imagine separating out the null warnings to an analyzer, because the type system understanding of the `?` would be logically separated from the flow analysis.
But there's non-trivial experience work in the IDE no matter what we do.
## Impact on type inference
``` c#
string? n = "Hello";
var s = n; // 'string' or 'string?' ?
```
If null state affects the type, then `s` above is of type `string`, because `n` is known to be non-null at the point of assignment. If not, then it is `string?` (but currently known not to be null).
This also affects which type is contributed to generic type inference:
``` c#
List<T> M<T>(T t) => new List<T>{ t };
void N(string? s)
{
if (s != null) WriteLine(M(s)[0].Length); // 1
if (s != null) { var l = M(s); l.Add(null); } // 2
}
```
If the type of `s` changes to `string` in the non-null context, then the calls to `M` infer `T` to be `string`, and return `List<string>`. Thus, `//1` is fine, but `//2` yields a warning that `null` is being passed to a non-null type.
Conversely, if the type of `s` remains `string?` in a non-null context, then the calls to `M` infer `T` to be `string?`, and return `List<string?>`. Thus, `//2` is fine, but `//1` yields a warning about a possible null dereference.
The currently proposed `!` operator changes the type of a nullable value to a non-nullable one. The deeper philosophy behind this, though, is simply that `!` should do the same to the value of a variable (that is not target typed) as a non-null null-state would have done.
``` c#
string? n = GetStringOrNull();
var l = n!.Length; // why no warning?
var s = n!;
```
With the current proposal, `n!` would be of type `string`, and there'd be no warning on the dereference because of that.
With the new proposal, `n!` would be of type `string?`, but the `!` would itself silence the warning on dereference.
In a way the new proposal unifies the target-typed and non-target-typed meanings of `!`. It is never about changing the type of the expression, just about silencing null warnings.
We need to get more specific about exactly what it means that it "silences the warnings".
## Conclusion
Let's roll with the new approach. As always, we'll keep an eye on whether that leads to a good experience, and are willing to revisit.
- Consider nullness an orthogonal aspect to the rest of the type being inferred
- If any contributing type is nullable, that should contribute nullness to the inference
- A `null` literal expression should contribute nullness to the inference, even though it doesn't otherwise contribute to the type
We should consider this for nullable value types as well.
# Structs with fields of non-nullable type
Structs can be created without going through a declared constructor, all fields being set to their default value. If those fields are of non-nullable reference type, their default value will still be null!
It seems we can chase this in three ways:
1. Not at all. We just aren't that ambitious.
2. We warn on all fields of structs that have non-nullable reference types. That's a *lot*! How do you "fix" it? Make them nullable? No version of the `!` operator works here, since the whole point is you don't control initialization from user code.
3. We warn whenever a struct that has such fields is created as a default value. In other words, we treat the type the same as a non-null reference type, recursively. (And we warn on passing for a type parameter constrained by `new()` or `struct`?)