Proposal: Improve analysis of [MaybeNull]T values
- Dominant language
- C#
- Stars
- 12.7k
- Forks
- 1.1k
- Avg merge
- 11h 1m
- Merged PRs (30d)
- 3
Description
Improve analysis of `[MaybeNull]T` values by adding a third possible flow state.
### Additional flow state
A third flow state is added that represents maybe null even when substituted with a non-nullable reference type. The additional state applies only to values of type parameters that are not constrained not nullable. Flow analysis will use two bits for each tracked value.
```C#
internal enum NullableFlowState : byte
{
///
/// Not null.
///
NotNull = 0b00,
///
/// Maybe null (type is nullable).
///
MaybeNull = 0b01,
///
/// Maybe null (type may be not nullable).
///
MaybeDefault = 0b11,
}
```
The merging rules are unchanged: `Join()` uses bitwise `|` and `Meet()` uses bitwise `&`.
### Member signatures affect method analysis
Attributes on members, including attributes on the containing method signature, are considered when analyzing the method body.
```C#
static T F1(IEnumerable e)
{
return e.FirstOrDefault(); // warning: returning [MaybeNull]T
}
[return: MaybeNull] static T F2(IEnumerable e)
{
return e.FirstOrDefault(); // ok
}
```
### No W warnings
Locals cannot use `[MaybeNull]` and neither can explicit casts. Because of that limitation, W warnings are not reported for unconstrained types.
```C#
static T Default()
{
T t = default(T); // ok: T t
return t; // warning: returning [MaybeNull]T
}
static U Cast([AllowNull]T t) where U : T
{
var u = (U)t; // ok: U u
return u; // warning: returning [MaybeNull]U
}
```
Warnings are produced for compound types though:
```C#
T[] array = new[] { default(T) }; // warning: default(T) nullability does not match T
List list = MakeList(default(T)); // warning: default(T) nullability does not match T arg
```
### No warning for null expressions
Expressions (of a type parameter type not constrained to not nullable) that may produce null values are treated as `MaybeDefault`. Warnings are not reported for the expressions directly.
```C#
static T Default()
{
T t = default(T); // ok
return t; // warning: returning [MaybeNull]T
}
static U As(T t) where U : class?
{
U u = t as U; // ok
return u; // warning: returning [MaybeNull]U
}
static T ConditionalAccess(IEnumerable? e) where T : class?
{
T t = e?.First(); // ok
return t; // warning: returning [MaybeNull]T
}
```
### Type inference
`[MaybeNull]` is ignored in method type inference.
```C#
T t = Identity(default(T)); // warning: default(T) nullability does not match T arg
```
Best type algorithm relies on the merging of states to choose [MaybeNull]T over T.
```C#
static T Choose(bool b, T t, [MaybeNull]U u) where U : T
{
return b ? t : u; // warning: returning [MaybeNull]T
}
```
### LDM Notes
- https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-09-11.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-11-11.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-11-13.md (adding new state to track `[MaybeNull] T`)
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.