csharplang/meetings/2020/LDM-2020-06-17.md
2020-06-18 16:31:16 -07:00

122 lines
No EOL
5.3 KiB
Markdown

# C# Language Design Meeting for June 17, 2020
## Agenda
1. Null-suppression & null-conditional operator
1. `parameter!` syntax
1. `T??`
## Discussion
### Null-suppression & null-conditional operator
Issue #3393
We generally agree that this is uninintended and unfortunate, essentially a spec bug. The
only question is whether to allow `!` at the end of a `?.`, as well as in the "middle". In
some sense
### `parameter!` syntax
This has been delayed because we haven't been able to agree on the syntax. The main contenders
are
```C#
void M(string param!)
void M(string param!!)
void M(string! param)
void M(string !param)
void M(checked string param)
void M(string param ?? throw)
void M(string param is not null)
void M(notnull string param)
void M(null checked string param)
void M(bikeshed string param)
void M([NullChecked("Helper")] string param)
/* contract precondition forms */
void M(string param) Requires.NotNull(param)
void M(string param) when param is not null
```
The simplest form, `void M(string param!)` is attractive, but looks very similar to the null
suppression operator. The biggest problem is that you can see them as having very different
meanings -- `!` in an expression silences nullable warnings, while `!` on a parameter does the
opposite, it actually produces an exception on nulls. However, the result of both forms is a
value which is treated by the compiler as not-null, so there is a way of seeing them as similar.
Moving it to the other side of the parameter name, `!param`, would resolve some of the similarity
with the null suppression operator, but it also looks a lot like the `not` prefix operator. There's
slightly less contradiction in these operators, but it still features a bit of syntactic overloading.
`string!` has a couple problems, including a suggestion that it's a part of the type (which it would not
be), and that sometimes you may want to use the operator on nullable types, like `AssertNotNull` methods.
It also wouldn't be usable in simple lambdas without types.
`checked` suggests integer over/underflow more than nullability.
`param!!` has some usefulness that we could provide a corresponding expression form -- `!`
suppresses null warnings, while `!!` actually checks for null and throws if it is found. On the
other hand, it also reads a bit strangely, especially since we're adding a new syntax form
instead of trying to reuse some forms we already have. On the other hand, the fact that it's
different enough to look different, while also short enough to be used commonly has a lot in
favor of it. In general we historically have a bias towards making new things strongly
distinguished from existing code, but shortly after introducing the feature we tend to wish that
things were less verbose and didn't draw as much attention in the code. On the other hand, the
nullable feature has a rule that it should not affect code semantics, while the purpose of this
feature is to affect code semantics. `param!!` could be seen as being too similar to other things
in the nullable feature, but to some people it also stands out because of the multiple operators.
We did a brief ranked choice vote and came up with the following ranking, not as definitive, just to
measure our current preferences:
1. `void M(string param!!)`
2. `void M(nullcheck string param)`
3. `void M([NullChecked(Helper)] string param)`
Many people don't have strong opinions, so we don't have a clear winner coming out.
**Conclusion**
We're getting closer to consensus, but we need to discuss this more and consider some of the
long-term consequences, as well as impact on other pieces of the language design.
### `T??`
Unfortunately there are multiple parsing ambiguities with `T??`:
```C#
(X??, Y?? y) t;
using (T?? t = u) { }
F((T?? t) => t);
```
This has left us looking back to the original syntax: `T?`. The original reason we rejected this
was that there could be confusion that `T?` means "maybe default," so that the type is nullable
if it's a reference type, but not nullable if it's a value type.
If we want to allow the `T?` syntax anyway, we need some syntax to specify that, for overrides,
we want the method with no constraints (unconstrained). This is because constraints cannot
generally be specified in overrides or explicit implementations, so previously `T?` always meant
`Nullable<T>`, but now it may not. We added the `class` constraint for nullable reference types,
but neither `T : class` nor `T : struct` help if the `T` is unconstrained. In essence, we need a
constraint that means unconstrained. Some options include:
```C#
override void M1<[Unconstrained]T,U>(T? x) // a
override void M1<T,U>(T? x) where T: object? // b
override void M1<T,U>(T? x) where T: unconstrained // c
override void M1<T,U>(T? x) where T: // d
override void M1<T,U>(T? x) where T: ? // e
override void M1<T,U>(T? x) where T: null // f
override void M1<T,U>(T? x) where T: class|struct // g
override void M1<T,U>(T? x) where T: class or struct // h
override void M1<T,U>(T? x) where T: cluct // joke
override void M1<T,U>(T? x) where T: default // i
```
**Conclusion**
The `default` constraint seems most reasonable. It would only be allowed in overrides and
explicit interface implementations, purely for the purpose of differentiating which method
is being overridden or implemented. Let's see if there are other problems.