
# 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.