csharplang/meetings/2018/LDM-2018-07-11.md

185 lines
5.7 KiB
Markdown
Raw Normal View History

2018-07-13 08:10:59 +02:00
# C# Language Design Notes for Jul 11, 2018
## Agenda
1. Controlling nullable reference types with feature flags
1. Interaction with NonNullTypesAttribute
1. Feature flag and 'warning waves'
1. How 'oblivious' null types interact with generics
1. 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);` <br> C#8: `var s = F(“”);` | List<string?> |
| T = string~ | C#8: `List<T> F<T>(T t);` <br> C#8: `var s = F(obliviousString);` | |
C#8: `List<T?> F<T>(T? t);` <br> C#8: `var s = F(obliviousString);` | |
| T = string? | `List<string?>` | C#7: `List<T> F<T>(T t);` <br> 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
```C#
List<T?> F<T>(T? t);
var s = F(obliviousString);
```
Decision: Type parameter wins. `s` is a nullable string.
For
```C#
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
```C#
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?
```C#
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
```C#
void M1<T>(T t) where T : I?, T : class?
```
to make `T` a nullable reference type. Or
```C#
void M1<T>(T t) where T : I, T: class
```
and now `T` is a non-nullable reference type.