Add LDM notes for 2019-03-13
This commit is contained in:
parent
e2e127e753
commit
0b6e6b3ba3
191
meetings/2019/LDM-2019-03-13.md
Normal file
191
meetings/2019/LDM-2019-03-13.md
Normal file
|
@ -0,0 +1,191 @@
|
|||
|
||||
# C# Language Design Meeting for March 13, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Interface "reabstraction" with default interface methods
|
||||
2. Precedence of the switch expression
|
||||
3. `or` keyword in patterns
|
||||
4. "Pure" null tests and the switch statement/expression
|
||||
|
||||
## Discussion
|
||||
|
||||
### Interface reabstraction
|
||||
|
||||
We previously specified our intent to permit reabstraction, where you could
|
||||
mark a derived implementation as "abstract" and force further implementors to
|
||||
provide an implementation.
|
||||
|
||||
We allow this in interfaces, so one argument for allowing is simply
|
||||
maintaining feature parity with classes, as we do in many other areas.
|
||||
|
||||
Another argument is that it would be very useful in interfaces because you
|
||||
would be able to provide an interface with a stronger contract than the base
|
||||
implementation. For instance, if you were implementing an interface that has
|
||||
a method which sorts a list, and you would like to provide an implementation
|
||||
which requires a sort with a stronger contract, like a stable sort, then you
|
||||
would want to mark the sort implementation as abstract. It may not be
|
||||
possible to write an implementation yourself and the implementation in the
|
||||
base may not be suitable because it doesn't implement the stronger contract
|
||||
(e.g., it implements an unstable sort).
|
||||
|
||||
There's an open question of how this would be implemented in the runtime and
|
||||
how expensive it would be.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The feature seems reasonable and if there were an easy, cheap implementation
|
||||
we would probably take it. However, we don't know if that's possible. We'll
|
||||
take a work item to investigate and see if it's feasible. We'd also like this
|
||||
to work for classes, but don't see it as strictly required if only interfaces
|
||||
are possible.
|
||||
|
||||
### Precedence of the switch expression
|
||||
|
||||
It's currently at the same precedence of the relational operators (like `<`,
|
||||
or `is` and `as`). There are some problems with this precedence, though. For
|
||||
example,
|
||||
|
||||
```C#
|
||||
x = e switch { .. } + 1 // syntax error
|
||||
```
|
||||
|
||||
This is because the `+` binds tighter than the switch, so this `switch ({ ...
|
||||
} + 1)`.
|
||||
|
||||
*Relational* precedence is also weird because of the following:
|
||||
|
||||
```C#
|
||||
x = a + b switch { ... } // this is `(a + b) switch { ... }`
|
||||
x = a & b switch { ... } // this is `a & (b switch { ... })`
|
||||
```
|
||||
|
||||
If we were to change to *primary expression* precedence, it would be `(b
|
||||
switch {...})` for both examples.
|
||||
|
||||
One question is what happens with `await`: what does `await e switch { ... }`
|
||||
do?
|
||||
|
||||
If we look at the `switch` expression as similar to the conditional expression,
|
||||
this would produce `(await e) switch { ... }`. More broadly, the switch looks
|
||||
a like a more complex conditional expression, so allowing expressions that
|
||||
are allowed on the left hand side of the conditional could be desirable for
|
||||
the switch as well. So, `a + b switch { ... }` is `(a + b) switch { ... }`.
|
||||
|
||||
However, one major difference between the conditional and the `switch` is that
|
||||
the switch can have expressions with arbitrary type, but conditional requires
|
||||
a `bool`, which allows for more complex expressions.
|
||||
|
||||
Here are some examples that may motivate the decision:
|
||||
|
||||
```C#
|
||||
|
||||
x + e switch {} * y
|
||||
|
||||
((x + e) switch {}) * y
|
||||
|
||||
_ = await e switch { ... };
|
||||
|
||||
_ = a ?? (b switch {
|
||||
...
|
||||
});
|
||||
|
||||
_ = (a ?? b) switch {
|
||||
...
|
||||
};
|
||||
|
||||
_ = a ?? b switch {
|
||||
...
|
||||
};
|
||||
|
||||
_ = 1 + o switch { int i => i, _ => 0 };
|
||||
|
||||
_ = a && b ? c : d;
|
||||
|
||||
_ = a ? b : d ? e : f;
|
||||
|
||||
_ = (string)s switch { ... };
|
||||
|
||||
_ = -x switch { ... };
|
||||
|
||||
_ = await x switch { ... };
|
||||
```
|
||||
|
||||
Looking at the examples, it's seeming like the space makes a big difference
|
||||
in readability. When there's a space between the operands, it's less obvious
|
||||
what the switch applies to, so binary operators are more ambiguous between
|
||||
`a ?? (b switch { ... })` and `(a ?? b) switch {...}`. However, the parenthesis
|
||||
for the first example look a bit stranger than the second, and some people
|
||||
feel that it looks more natural to the switch to bind to only `b`.
|
||||
|
||||
There's also a fairly strong feeling that the unary operators, like `-` and
|
||||
cast, should bind more strongly.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
It seems like a new precedence level between unary and multiplicative is the
|
||||
right fit. For places where either interpretation could be possible
|
||||
(`await`), this doesn't feel like an unreasonable decision. For places where
|
||||
one interpretation seems preferred (other unary operators and binary
|
||||
operators) this choice fits for both.
|
||||
|
||||
### Patterns with `or`
|
||||
|
||||
Should we make
|
||||
|
||||
```C#
|
||||
switch (e)
|
||||
{
|
||||
case X or:
|
||||
}
|
||||
```
|
||||
|
||||
a warning on `or` as an illegal variable name? This would assist parsing
|
||||
because we would not have to do any parsing lookahead to figure out if
|
||||
this is a designation, vs an `or` pattern (that doesn't exist yet).
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Unfortunately, we already shipped this, so it would be a breaking change.
|
||||
Since we can resolve this through lookahead, we don't think this example is
|
||||
severe enough to warrant a new warning. If we see something more complicated
|
||||
to resolve, we may reconsider.
|
||||
|
||||
### Where to produce warnings for "null test" in switch
|
||||
|
||||
```C#
|
||||
switch (s)
|
||||
{
|
||||
case null when s.Length == 1:
|
||||
break;
|
||||
case null when M(s = "foo"):
|
||||
break;
|
||||
case _ when s.Length == 2:
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
We have decided `null` is a "pure" null test and we will update the null
|
||||
state for `s`. The question is where we update that state:
|
||||
|
||||
1. To the entry of the switch and all previous cases?
|
||||
2. To the branch of the switch only?
|
||||
2. Just to the forward paths of the flow analysis?
|
||||
|
||||
One problem with relying just on the flow analysis is that disjoint checks
|
||||
could be reordered and affect the diagnostics produced, even though the bug
|
||||
would be present regardless of the ordering, since the check would actually
|
||||
be verifying the nullability of the input, which is immutable across the
|
||||
switch arms.
|
||||
|
||||
The other problem is what variable state to update. If we copy values from
|
||||
the switch and test against the copy, that would be a separate state that
|
||||
would need to be tracked, since the original expression could be modified
|
||||
within the switch.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's look at only using forward flow propagation, but define the ordering as
|
||||
looking at the patterns of each arm in order, then use the following flow
|
||||
graph. Notably, if there was no declared variable for the switch expression,
|
||||
we will synthesize one.
|
|
@ -75,34 +75,16 @@ We now have a proposed runtime implementation for reabstraction. See https://gi
|
|||
|
||||
- Confirm: `partial` on a method declaration implies `private`, and no access modifier is permitted?
|
||||
|
||||
Overview of meetings and agendas for 2019
|
||||
|
||||
## Mar 13, 2019
|
||||
|
||||
#### Default Interface Methods
|
||||
See also https://github.com/dotnet/csharplang/issues/406
|
||||
[C# Language Design Notes for March 13, 2019](LDM-2019-03-13.md)
|
||||
|
||||
- Reabstraction (open)
|
||||
- explicit interface abstract overrides in classes (open)
|
||||
|
||||
#### Pattern-Matching
|
||||
See also https://github.com/dotnet/csharplang/issues/2095
|
||||
|
||||
- Propose to change precedence of switch expression to primary (open)
|
||||
|
||||
The switch expression is currently at *relational* precedence. I propose to change it to *primary* precedence. See [#2331](https://github.com/dotnet/csharplang/issues/2331) for details.
|
||||
|
||||
- Reserve `and` and `or` in patterns (open)
|
||||
|
||||
In anticipation of possibly permitting `and` and `or` as pattern combinators in the future, we should forbid (or at least warn) when these identifiers are used as the designator in a declaration or recursive pattern. Otherwise it would be a breaking change.
|
||||
|
||||
- To where do null inferences flow from a pattern in a `switch`? (open)
|
||||
|
||||
1. To the entry of the switch and all previous cases
|
||||
2. To that branch of the switch only
|
||||
3. To all code that follows the test in logical order
|
||||
|
||||
# C# Language Design Notes for 2019
|
||||
|
||||
Overview of meetings and agendas for 2019
|
||||
1. Interface "reabstraction" with default interface implementations
|
||||
2. Precedence of the switch expression
|
||||
3. `or` keyword in patterns
|
||||
4. "Pure" null tests and the switch statement/expression
|
||||
|
||||
## Mar 25, 2019
|
||||
|
||||
|
|
Loading…
Reference in a new issue