dotnet / dotnet/csharplang

Proposal: Improve analysis of [MaybeNull]T values

Open
#2,946 3 comments 0 reactions 1 assignee Claimed by @jcouv View on GitHub
Implemented Needs ECMA Spec Proposal Proposal champion
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.