5 KiB
C# Language Design Notes for Oct 11, 2017
Agenda
We looked at the Oct 4 design review feedback for nullable reference types, and considered how to react to it.
- Philosophy
- Switches
- Dotted names
- Type narrowing
- Dammit operator type narrowing
- Dammit operator stickiness
- Array covariance
- Null warnings
Philosophy
Feedback: We should view this as a linting tool. We have to make most existing code pass muster, and it's ok with a little more complexity and forgiveness in the rules to achieve that.
Conclusion
We agree with this general philosophy, and it is helpful to apply it to specific decisions.
Switches
Feedback: Don't have many switches and levers. Just "on" or "off". "Off" would suppress the warnings, but the ?
syntax would still be allowed. Make the hard decisions once and for all at the language level, rather than leave people with too many options.
Conclusion
This is a good philosophy. We do think that there's possibly room for an "Xtreme" mode as well, for people that care more about catching more cases regardless of inconvenience.
Dotted names
Feedback: We should track dotted names, and be very forgiving about what invalidates the null state. Otherwise it violates the general philosophy and complains about too much existing code. Things that would invalidate non-null-ness of a dotted chain is assigning to (or maybe passing to an out or ref parameter) the variable itself or any mutable prefix.
Conclusion
Agree! If we adopt an Xtreme mode, this is probably one of the places where it would be harsher.
Type narrowing
Feedback: when a variable of a nullable reference type (e.g. string?
) is known to not be null, we should consider its value to be of the narrower type string
.
void M(string? n)
{
if (n == null) return;
var s = n; // s is string, not string?
var l = s.Length; // ok
n = null; // ok
s = null; // warning
}
We previously abandoned this approach, because it doesn't work for e.g. type parameters, where we don't know if they are nullable reference types or not, and don't necessarily have an underlying non-nullable type to narrow to.
Conclusion
This is one of those places where we should forego simplicity for friendliness. We should adopt a hybrid approach: When a type is a known nullable reference type, then having a non-null state should narrow its type. When it is a type parameter that might be instantiated with nullable reference types, then we can't narrow it, and should just keep track of its null state.
Dammit operator type narrowing
Feedback: The dammit operator should also narrow the type of a nullable reference to be nonnullable.
void M(string? n)
{
var s = n!; // s is string, not string?
var l = s.Length; // ok
}
T[] MakeArray<T>(T v)
{
return new T[] { v };
}
void M(string? n)
{
var a = MakeArray(n!); // a is string[], not string?[]
var l = a[0].Length; // ok
}
Conclusion
`! should keep its warning suppression, but also narrow the outermost type when it can, to match the new behavior when null-state is non-null.
There's hesitation because of the muddiness of using !
for two different things. But we don't have a better idea. Explicit casts are quite verbose. We may need to revisit later, but for now, !
suppresses warnings and de-nullifies the type when it can.
Dammit operator stickiness
Feedback: The dammit operator lacks "stickiness" - you have to keep applying it (or introduce another local).
One idea is to maybe have some top-level "assertion" that would declare a thing not-null for the whole scope.
Another idea is to have s!
influence null state for flow analysis, staying "valid" for as long as a non-null state would have.
string? s = ...
if (s != null && s.Length == 5) ... // It flows here
M(s!, s); // why not here?
It would sort of make s!
mean the same as s = s!
.
There are also cases where you wouldn't want it to be sticky, e.g. when you are using !
to shut up a specific unannotated API that is lacking a ?
. Here you don't use !
in the meaning of "this is really not null", but to the effect of "I am actually fine passing a null here". That meaning shouldn't really be contagious to subsequent lines.
Conclusion
We don't know what, if anything, to do here. For now we'll leave it as is.
Array covariance
Feedback: Arrays are (unsafely) covariant over reference types, and for consistency we should also make them covariant over nullability.
Conclusion
This is probably right. We'll think about this more, but let's go with covariance for now.
Null warnings
Feedback: Fine to warn in most places where a null value is given a nonnullable type, but we should beware of a "sea of warnings" effect. Specifically, we shouldn't warn on array creation, as in new string[10]
, even though it creates a hole array of undesired nulls, because it is so common in current code, and couldn't have been done "safely" before.
Conclusion
We agree. Xtreme mode, if we adopt that, may warn on array creation, though.