5.4 KiB
C# Language Design Meeting for March 6th, 2019
Agenda
Open issues:
- Pure checks in the switch expression
- Nullable analysis of unreachable code
- Warnings about nullability on expressions with errors
- Handling of type parameters that cannot be annotated
- Should anonymous type fields have top-level nullability?
- Element-wise analysis of tuple conversions
Discussion
Pure checks in switch expression
Example:
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
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?
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
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:
// 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.