csharplang/meetings/2018/LDM-2018-07-11.md
2018-07-16 16:52:12 -07:00

5.7 KiB

C# Language Design Notes for Jul 11, 2018

Agenda

  1. Controlling nullable reference types with feature flags
  2. Interaction with NonNullTypesAttribute
  3. Feature flag and 'warning waves'
  4. How 'oblivious' null types interact with generics
  5. Nullable and interface generic constraints

Nullable feature flag

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:

  • Turn the feature on and disable all the nullable warnings
  • OR turn feature off and suppress the warning about the feature being off

Interaction with NonNullTypesAttribute

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?

  • We usually don't poison new feature attributes for older compilers (e.g., dynamic, params). We just provide errors in new compilers.
  • This has not been a problem in the past

--

Nullable references and warning waves

Q: Do we want to include the nullable references in Warning waves? Would "v8" include all nullable warnings?

  • If the feature flag affects the NonNullTypes context, it doesn't just impact warnings
  • The benefit of warning waves is that tooling support wouldn't need to be customized for this feature

Proposal: 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

'Oblivious' types and generic parameters

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

Generic substitution

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.

Nullable and generic constraints

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.