Add LDM notes for Dec. 18, 2019
This commit is contained in:
parent
ee1bf2bd8e
commit
f2b71591be
|
@ -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.
|
147
meetings/2019/LDM-2019-12-18.md
Normal file
147
meetings/2019/LDM-2019-12-18.md
Normal 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.
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue