dotnet/csharplang

`field` keyword nullability: short and long term solutions

RikkiGibson opened this issue · 1 comments

See also https://github.com/dotnet/csharplang/blob/main/proposals/field-keyword.md#nullability
See also #8360

We've been exploring how to provide a great experience for nullable analysis of properties using field keyword. We have considered introducing interprocedural analysis through concepts like "getter null-resilience" or through full interprocedural analysis of the constructor and accessors.

We need more time in order to arrive at the right place with these ideas. However, there is not much time left to get changes into .NET 9. So, we need to start thinking about short term versus long term solutions to this problem.

Short-term solution

It was previously suggested that we could handle the short term by simply treating properties that use the field keyword the same way we treat auto-properties today. This means that "lazy" property cases will need to suppress constructor warnings. The constructor warning will serve as a sign that users writing lazy properties are wandering into "unchecked" territory. Specifically, the field's initial flow state in the accessors will be not-null, but the value will be null at runtime.

class C
{
    public string Prop
    {
        get => field ??= GetDefault();
        set => field = value;
    } // fix: assign 'null!' in property initializer

    public C() { } // warning: Prop should have non-null state when exiting constructor.
}

Alternative short-term solutions include:

  • Treat field-backed properties similarly to auto-properties per the above, but also add recognition of [field: MaybeNull, AllowNull] now, so that users can use that instead of = null!;. Change the initial state of the field within property accessors accordingly in presence of such attributes.
  • For a non-nullable reference type property, make the field's nullable annotation oblivious, and don't check property initialization in constructors. In this case, there will be no signal to authors of lazy properties that unexpected nulls may flow into their code.
  • Don't check property initialization in constructors, and make the field's nullable annotation always match the property. This is a similar safety hole as the oblivious solution, except that this solution would warn when writing possible null values into backing fields of non-nullable properties.

Long-term solution

We want to ensure that the short-term solution will transition smoothly to the long-term solution.

Some of the long-term solutions we have considered include:

  1. analyze the get accessor to decide the nullable annotation of the field, or,
  2. analyze the setter and constructor in order to flow the nullable state of the field into the getter.

When considering the above scenario as written, similar observable behavior will occur with both long-term solutions.

  • No nullable warnings will be reported.
  • The field will have maybe-null initial state in the get accessor.
    • With solution (1), this is because the getter is null-resilient.
    • With solution (2), this is because the field is maybe-null when exiting the constructor.

Essentially, the biggest risk here is that early adopters may add = null!; initializers to their code, which they will forget to remove after upgrading to our long-term solution. Until that null! is removed, there is a possibility of the user introducing null-safety bugs into their code.