csharplang/meetings/2019/LDM-2019-03-06.md

175 lines
5.4 KiB
Markdown
Raw Normal View History

2019-03-11 08:36:33 +01:00
# C# Language Design Meeting for March 6th, 2019
## Agenda
Open issues:
1. Pure checks in the switch expression
2. Nullable analysis of unreachable code
3. Warnings about nullability on expressions with errors
4. Handling of type parameters that cannot be annotated
5. Should anonymous type fields have top-level nullability?
6. Element-wise analysis of tuple conversions
## Discussion
### Pure checks in switch expression
Example:
```C#
void M(object x)
{
_ = x switch
{
1 => x.ToString(),
{} => x.ToString(), // is this a "pure" null test?
}
}
```
The original proposal was that certain tests, like `is {}` are "pure" null
checks because there is no reason to perform such a test unless you are
checking if the input is null. The switch expression was not decided, but it
may be confusing to have a pattern check for null in an `is` expression, but
not produce the same nullable semantics for the same pattern in `switch`.
However, it's possible that the user didn't intend `{}` to be a null test in
the previous example, but just a "catch-all" case. If we use the original
interpretation of "pure null test" then this would not meet the bar, as there
is a meaning which could not be a null test. However, this would produce
a different meaning for `{}` in an `is` expression, as opposed to a `switch`.
**Conclusion**
`{}` is proving contentious as a "pure" null check in a switch. One
alternative which we can all agree on is removing `{}` as a pure null check
in the `is` expression, as well. This would remove the problem of consistency
all together.
### Nullable analysis of unreachable code
```C#
static void M(bool b, string s)
{
var t = (b || true) ? s : null;
t.ToString(); // warning?
}
```
**Conclusion**
Diagnostics will not be produced because of nullability analysis on
expressions which are non-nullable. This conforms to what we do for definite
assignment, where everything is treated as defintely assigned in unreachable
code.
We accept a proposed rule where all expressions which are unreachable are
non-nullable (even `null`), in service of the above principle.
### Warnings about nullability on expressions with errors
Should we provide warnings about nullability in cases where one of the
symbols being considered has an error, like a variable which was declared
but never initialized?
**Conclusion**
We will not provide nullable diagnostics on symbols with errors, because we
do not know exactly how the types will be altered to remove the errors, so
the warnings may not be useful and are less important than the errors already
reported.
### Handling of type parameters that cannot be annotated
`e?.M()` used a statement is fine and we shouldn't care about the return type.
Similarly, `e ?? M()`.
The main unfortunate case is `default`, and the most annoying case is probably
implementing code like `GetFirstOrDefault()` where the only way to implement
it is to suppress the warning. Moreover, if the method is annotated with
`MaybeNull` it seems like you have to mention that the value could be null
twice: once in the signature of the method, and once in the body.
**Conclusion**
We like the list as is. We considered using annotations like `MaybeNull` in
things like `return` statements, but aren't willing to go that far right now.
More broadly, we may want to consider whether the annotations should apply to
the implementer of a method, instead of just the users of the method, but we
are not going to do so now.
### Should anonymous type fields have top-level nullability?
```C#
static T Identity<T>(T t) => t;
static void F(string x, string? y)
{
var a = new { x };
a.x.ToString(); // ok
a = new { x = y }; // warning?
a.x.ToString(); // warning
Identity(a).x.ToString(); // warning?
}
```
Our primary concern here is LINQ query expression, which functionally produce
anonymous types that are then consumed through further functions.
**Conclusion**
Yes, we want to track the inferred type of the anonymous type, so for
```C#
void M(string x, string? y)
{
var a = new { x };
a = new { x = y };
}
```
`a` would be of type `{ string! x }` and the subsequent assignment would
produce a warning.
### Element-wise analysis of tuple conversions
We discussed the behavior of nested nullability in tuple expressions for
the following example:
```C#
// current behavior:
static T Id<T>(T t) => t;
static void M(string? x, string y)
{
(string, string) t = (x, y); // warning: (string?, string) mismatch
t.Item1.ToString(); // warning: Item1 may be null
var x = Id(t); // what is the inferred type of x?
}
```
The warnings are present regardless of the design. The only question is if
the type of `x` is `(string, string)` or `(string?, string)`. If tuples
behaved like the underlying `ValueTuple`, the inferred type would be
`(string, string)` since the tracking of the `Item1` field does not change
the type of the container.
However, if we pretended that the elements of the tuple were individual
local variables, we would infer `(string?, string)` because the tracked state
of a local variable is used in type inference.
**Conclusion**
The inferred type is `(string, string)`. Aside from difficulties in nailing
down exactly what the semantics of "tracked like locals" means (presumably it
wouldn't apply to tuple fields of a second type?) there is a simplicity to
making tuples, constructed types, and anonymous types behave roughly
identical through type inference.