meetings/2018/LDM-2018-07-11.md
A nullable feature flag has been proposed that turns on the new warnings for the nullable reference type feature. A feature flag is proposed because new warnings could be generated for existing code, so the developer must opt-in to the new warnings.
The question is: what does the feature flag do, exactly?
Binding and emit are unaffected by feature flag, so code will not behave any differently with it enabled. If the feature is on you get nullability warnings. If the feature is off and you annotate a reference type with '?', a warning is produced that the feature is disabled, and the IDE should offer to turn the feature on. Without a warning, a developer could think they are using the feature because they've annotated their references, but in fact no warnings are being provided.
Question: What if a developer wants to annotate their public API, but they don't want to consume the feature in their own code?
Answer There are two options:
Related to the feature flag, a NonNullTypes attribute is proposed that decides
how unannotated types are interpreted by the compiler. If NonNullTypes(true)
is present, unannotated types are assumed to be non-null. If it is absent
or NonNullTypes(false) is present, then unannotated types are "oblivious"
and don't produce warnings.
How do the attribute and the feature flag interoperate? Two independently varying variables: - Assembly is annotated - Warnings are produced
Here are the possibilities:
| Warning | No warning | |
|---|---|---|
| Annotated | C# 8 feature on | C# 8 feature off w/ suppress |
| Unannotated | N/A | C# 7 |
Each of these seem reasonable.
Q: What about external annotations?
A: Don't know enough about their design yet to decide
Conclusion
Proposal accepted.
For the NonNullTypes attribute, do we block it for older compilers?
dynamic, params). We just provide errors in new compilers.--
Q: Do we want to include the nullable references in Warning waves? Would "v8" include all nullable warnings?
NonNullTypes context, it doesn't
just impact warningsProposal: If you use the feature, it updates the warning wave and sets the
NonNullTypes setting.
Concern: If so, when you get the new warning wave, now you get nullable and all the other warnings. If you only want one set but not the other, there's no way to split them out.
Conclusion
Don't connect nullable warnings to warning waves
Q: Does M<T?> require T to be a non-nullable reference or value type?
A: Yes.
If there's a mix of generic types where T is oblivious or non-oblivious,
say
The question is what to do about the following cases:
| List<T!> | List<T~> | List<T?> | |
|---|---|---|---|
| T = string! | C#7: List<T> F<T>(T t); | ||
C#8: var s = F(“”); | List<string?> | ||
| T = string~ | C#8: List<T> F<T>(T t); | ||
C#8: var s = F(obliviousString); | |||
C#8: List<T?> F<T>(T? t); | |||
C#8: var s = F(obliviousString); | |||
| T = string? | List<string?> | C#7: List<T> F<T>(T t); | |
C#8: var s = F(nullString); |
Note that the previous table uses a shorthand that is not meant to be actual C# syntax.
T! means non-null reference type, T~ means oblivious type, and T? means nullable type. T?
is the only syntax which is actually expected to appear in valid C# programs.
For
List<T?> F<T>(T? t);
var s = F(obliviousString);
Decision: Type parameter wins. s is a nullable string.
For
List<T> F<T>(T t)
var s = F(obliviousString)
Is s oblivious? Does this "flow" oblivious all over?
Proposal: "Collapse" oblivious types as soon as possible
So the previous type substitution, is List<string!>. Even outside of type
substitution, oblivious types won't be inferred, so in
var s = obliviousString;, s would be inferred as string!, not an
oblivious type.
Conclusion
Proposal accepted.
For
List<T> F<T>(T t)
var s = F(nullString);
The substitution is the argument type. s is a nullable string.
First, it is decided that T? requires T be known as a reference type.
Notably, an interface constraint alone is not sufficient.
What does the following do?
void M1<T>(T t) where T : I, T : class?
This is a warning or error, because T : I implicitly means that I is
a non-nullable type and class? is a nullable reference type. The
constraints are contradictory. There are two fixes. Either
void M1<T>(T t) where T : I?, T : class?
to make T a nullable reference type. Or
void M1<T>(T t) where T : I, T: class
and now T is a non-nullable reference type.