Proposal: A switch with a pure test sets the state of the tested variable to maybe-null at the entrance of the switch.
In that context, I think Mads is pushing back on {}
counting as a “pure test” in a switch
.
If I understood, he is saying a {} => …
branch on its own doesn’t open the possibility of a null (it’s not a pure test). But a {} => …
branch followed by another branch would be.
Note: if we treat {}
as a pure test, then we could offer a fixer to change it to _
if you didn't want to imply a null test.
Resolved in LDM 3/6/2019: {}
and derived is Base
are not "pure tests".
void M(object x)
{
_ = x switch
{
var y when Foo() => x.ToString(), // warn for dereference?
{} => x.ToString(),
null => "",
};
}
Switch expression with {}
case, but no other branch:
void M(object x)
{
// x is treated a maybe-null in the switch because of the pure test `{}`
_ = x switch
{
1 => x.ToString(); // dereference is ok because pattern `1` tells us `x` isn't null in this branch
{} => x.ToString(); // dereference is ok because of pattern `{}`
// a branch is missing here (incomplete for nullability warning)
};
x.ToString(); // ok, because non-throwing branches all have non-null `x`
}
Switch expression with {}
case followed by some other branch:
void M(object x)
{
// x is treated a maybe-null in the switch because of the pure test `{}`
_ = x switch
{
1 => x.ToString(); // ok to dereference because of pattern `1`
{} => x.ToString(); // ok to dereference because of pattern `{}`
anything /* ex: null, _, var y */ => x.ToString(); // warn on dereferencing x
};
x.ToString(); // warn because some branches exiting switch have maybe-null `x`
}
Switch expression with nested {}
test followed by some other branch:
void M()
{
A a = new A() { B = "" };
// `a.B` is treated a maybe-null in the switch because of the pure test `{}`
_ = a switch
{
A { B is {} } => a.B.ToString(); // ok to dereference because of patterns
_ => a.B.ToString(); // warn
};
a.B.ToString(); // warn
}
Incomplete switch
expression:
void M(object? x)
{
_ = x switch
{
object _ => ...
// incomplete for nullability
};
x.ToString(); // warn
}
Pure test affects all branches in a switch
expression:
void M(object? x)
{
// x is treated a maybe-null in the switch because of the pure test `{}`
_ = x switch
{
var y when Foo() => y.ToString(), // warn for dereference
{} => ""
// incomplete for nullability
};
x.ToString(); // warn
}
Note only cases that are pure null tests have this effect. So cases like _
, var y
, or C {}
do not change the state of x
when exiting the switch
expression.
Switch statement with {}
case:
void M(object x)
{
switch (x)
{
case 1:
x.ToString(); // ok
break;
case {}: // opens up a null branch (implicit default branch below)
x.ToString(); // ok
break;
}
x.ToString(); // warn
}
Switch statement with null
case:
void M(object x)
{
switch (x)
{
case 1:
x.ToString(); // ok
break;
case null: // opens up a null branch (present branch)
x.ToString(); // warn
break;
}
x.ToString(); // warn
}
Note only cases that are pure null tests have this effect. So cases like _
, var y
, or C {}
do not change the state of x
when exiting the switch
statement.
Pure tests:
- x == null
- x != null
- (Type)x == null
- (Type)x != null
- x is null
- x is { }
- derived is Base
- x?.F
- x?[i]
- x ?? y
- bool [EqualsBehavior]MyEquals(object other)
Not pure tests:
- x is Y // where Y is not a base of X
- x is Y y
- x is Y _
- x is C { Property = 3 }
- TryGetValue([NotNullWhenTrue])
- [NotNulllWhenFalse] string.IsNullOrEmpty(s)