185 lines
5.7 KiB
Markdown
185 lines
5.7 KiB
Markdown
|
|
||
|
# 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.
|