Add LDM notes for Dec. 18, 2019

This commit is contained in:
Andy Gocke 2019-12-18 15:57:51 -08:00
parent ee1bf2bd8e
commit f2b71591be
3 changed files with 154 additions and 128 deletions

View file

@ -1,122 +0,0 @@
# C# Language Design Notes for Dec. 16, 2019
## Agenda
1. Switch expression as a statement expression
2. Triage
## Discussion
### Switch expression as a statement expression
https://github.com/dotnet/csharplang/issues/2860
The proposal being discussed is whether to allow the switch expression without a discard:
```C#
_ = a switch
{
...
};
// becomes
a switch
{
...
};
```
One of the against is that it makes for a confusing decision in the language as to whether you
use a switch expression or switch statement. Right now the guidance is simple: if you are in a
statement context, use a statement. If you have an expression context, use a switch expression.
Now, for a statement, you could use either a switch statement, or a switch expression in
statement form. It's not clear which.
One way of resolving this is that these are two parallel features that provide similar features
in a slightly different syntax and semantics. Then the answer simply becomes, use whichever one
you like better. If the new switch expression form includes exhaustiveness checking, that would
be a reason to use or not to use it, aside from the syntax differences. Similarly, the switch
statement ability to `goto` another case is a reason to use that form. However, if we accept
that, the switch expression feels artificially limited. To provide a satisfactory parallel
feature we have to augment the switch expression to allow for statements in the switch arms. Then
there is a potential new set of features: block in switch expressions.
On the other hand, this feels like feature creep. The original proposal was quite simple: allow
users to elide `_ =` and remove the requirements for the arms to have a common type. While we may
want to have a number of different new features for switch expression to make it comparable to
the switch statement, there's value in doing the feature as-is, and adding those features later.
This is contingent on us being fairly confident that the new features can be added without
breaking changes, but there's a fair amount of confidence that we know where we would go with the
feature. This perspective would require us to keep certain behaviors to ensure that the switch
expression keeps its differences from the switch statement. For instance, the new switch
expression-as-statement would have to check exhaustiveness if we see it as a strict improvement
for the switch expression.
Lastly, we all find the proposed switch expression-as-statement requiring a semicolon i.e.,
```C#
a switch
{
b => ...,
c => ...
}; // semicolon required
```
as being extremely ugly.
**Conclusion**
Rejected as-is. We'd be interested in a new proposal on this topic, addressing many of the
concerns that we brought up today.
### Triage
#### Definite assignment of private reference fields
**Conclusion**
Accepted for warning waves v1, wherever that is triaged.
#### Remove restriction on yielding a value in the body of a try block
Also for async iterators.
Issue #2949
**Conclusion**
Accepted, Any Time.
#### Generic user-defined operators
Issue #813
**Conclusion**
There's no syntax in the invocation to specify the type arguments, in case inference doesn't
succeed, and we think almost any syntax in the invocation location would be ugly. In addition, we
don't have a lot of examples of why this would be significantly better than alternatives (like
writing a method).
Rejected.
#### Support for method argument names in `nameof`
Issue #373
It looks like there's a significant breaking change if we allow the parameter names to be in
scope generally.
```C#
const int p = 3;
[Attribute(Property = p)]
void M(int p) { }
```
If we just allow `nameof` to have special scoping to allow the names in the method declaration to
be in scope, then there's no language breaking change. The scoping rules would prefer names in
the method header (including type parameters) over rules in the rest of the program.
**Conclusion**
Accepted, Any Time.

View file

@ -0,0 +1,147 @@
# C# Language Design Meeting for Dec. 18, 2019
## Agenda
1. Nullable issues
a. Pure null checks
b. Consider `var?`
## Discussion
### Pure null checks
We have the following syntax which semantically check for null in the language:
* `e is null`
* `e == null`
* `e is {}`
* `e is object`
However, for nullability we have a separate notion of a "pure" null check that
causes a null branch to appear, even if the variable being tested was declared
not null, or vice versa.
An example of why this would matter is
```C#
var current = myLinkedList.Head; // assume Head is nullable oblivious/unannotated
while (current is object)
{
...
current = current.Next; // assume oblivious
}
current.ToString(); // only warns if `is object` is a pure null test
```
We previously established that all "pure" null checks contain the word `null` in them, meaning
that only `e is null` and `e == null` are pure null checks today. There is a proposal that we
should unify all forms that are semantically equivalent, regardless of syntax.
There is also a proposal to expend this even to places which are not "pure" checks, i.e.
they have semantic effect larger than just checking for null. For instance, we could
also check `e is object o`, which also introduces a variable `o`. We came up with
the following list of potential checks:
```
e is null // pure
e is object // Proposed
e is [System.]Object // Proposed
e is object _
e is object x
s is string // s denotes expr of static type string
s is string x
o is string x
e is {} // Proposed
e is {} _
e is {} x
e == null // pure
e != null // pure
e is not null // pure
e is not object // etc...
```
All parties argue that other positions are confusing as to why something is a pure
null check and something else, that's very similar, is not. It seems like drawing
any particular line will always imply that something similar could be confusing.
One difference between versions that check between a semantically pure null check, i.e. a piece
of syntax that has no other meaning than testing for null, is that if there is a pure null check
then any warning is definitely a bug in user code: either the check is superfluous, or there is
an actual safety issue. If the check is not pure, there may not be a bug, because the check may
not actually be superfluous and this may be a spurious warning.
Given that the pure null checking is useful, it's mainly about finding the right balance between
helping the user find bugs in their code and finding a set of rules that are also easily
understandable. The main argument against broadening beyond our current rule is that "pure checks
contain the word 'null'" is a simple rule, and adding warnings in an update is a heavy way to
address the issue.
On the other hand, we have changed nullable warnings multiple times already, plan to
do it again, and have warned people that nullable warnings may be in flux for a time.
If the feature is also meant to react to user intent, and if we believe `x is object`
is intended by the user to be a null check, then making it a pure null check would
be correctly responding to user intent.
We could also decide based on whether or not we want to suppress certain patterns. If
we believe `x is object` or `x is {}` aren't good ways to test for null, then making
them not pure null checks would encourage users not to use it. This did not seem a
compelling position for anyone in LDM.
**Conclusion**
We agree that we should broaden the set of pure null checks. We agree that `x is object` should
be a pure null check. Moreover this should be based on the type in the `is` expression, meaning
that any type `T` that binds to `System.Object` in `x is T` would be a pure null check. We also
agree that `x is {}` is a pure null check.
None of `x is object _`, `x is object o`, `x is {} _`, or `x is {} o` are pure null checks.
### `var?`
At this point we've seen a large amount of code that requires people spell out the
type instead of using var, because code may assign `null` later.
An example,
```C#
var current = myLinkedList.Head; // annotated not null
while (current is object)
{
...
current = current.Next; // warning, Next is annotated nullable, but current is non-null
}
```
One way to deal with this is to allow `var?`,
```C#
var? current = myLinkedList.Head;
// now current is nullable, but the flow state is non-null
current.ToString(); // no warning, because the flow analysis says it's not null
```
This would let people express that the think the variable may be assigned null later on.
On the other hand, we could just permit these assignments when using `var`, and use flow analysis
to ensure safety.
```C#
var current = myLinkedList.Head;
current = null; // no warning because var is nullable
current.ToString(); // warning, the flow state says this may be null
```
This would allow users to be explicit when they want to make sure not to assign
null to a type, but they have to spell out the type.
**Conclusion**
Make `var` have a nullable annotated type and infer the flow type as normal.

View file

@ -10,12 +10,6 @@
- *Triage milestones*
- *Design review*
## Dec 18, 2019
- close on compat issue with duplicate implementations/constraints modulo nullability differences (Julien)
- Irksome nullable issues to revisit (Jared)
- re-discuss `x is object` being a pure null test
- consider `var?` to avoid "un-var'ing" for nullability (e.g. https://github.com/microsoft/vs-threading/pull/538/file)
## Dec 9, 2019
@ -33,6 +27,13 @@
Overview of meetings and agendas for 2019
## Dec 18, 2019
[C# Language Design Notes for Dec 18, 2019](LDM-2019-12-18.md)
1. Pure null checks
2. `var?`
## Dec 16, 2019
[C# Language Design Notes for Dec 16, 2019](LDM-2019-12-16.md)