dotnet / csharplang

The official repo for the design of the C# programming language

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Open issue: nullability analysis of collection expressions

jcouv opened this issue · comments

In a scenario like the following:

                object? maybeNull = null;
                C<object> c1 = [maybeNull];

                class C<T> : IEnumerable<T>
                {
                    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null!;
                    IEnumerator<T> IEnumerable<T>.GetEnumerator() => throw null!;
                    public void Add(T t) { }
                }

There are three potential reasons to warn:

  1. Language conversion rule: we're converting a maybeNull element to an object! iteration type
  2. Canonical lowering: we're passing a maybeNull argument to the Add(object!) method
  3. Potential lowering strategy: if we used some alternative lowering strategy, such as using AddRange(IEnumerable<object!>) or CopyTo(List<object!>, int), we'd be passing IEnumerable<object?> or List<object?> arguments to those methods.

Here's the language rule relevant to # 1:

The following implicit collection expression conversions exist from a collection expression:
...
To a struct or class type that implements System.Collections.Generic.IEnumerable where:
For each element Ei there is an implicit conversion to T.
...

We're not getting a clear agreement on prioritizing # 1 (analyze conversion rule) vs. # 2 (analyze canonical lowering), so we'll wait for a decision from LDM. Maybe we should warn for both reasons.
We're leaning to avoid # 3 (analyze optimized lowerings) as well-behaved APIs should behave like the canonical APIs.

If we just did the first check, then the following would warn:

C<object> c1 = [maybeNull]; // warning because the iteration type is `object!` but the element is maybe-null `object`
class C<T> : IEnumerable<T>
{
  ...
  public void Add(T? t) { }
}

If we just did the second check, then the following would warn:

C<object> c1 = [maybeNull]; // warning because we're calling `Add(object!)` with a maybe-null `object` argument
class C<T> : IEnumerable<T?>
{
  ...
  public void Add(T t) { }
}

As a user, either warnings would be fine. I'm getting a NRT warning, and it's effectively telling me that object! and object? are going to be a problem. That's literally all that matters, and the exact verbiage isn't really going to matter to me :)

How about a combination of option 1 with another language rule - if it has IEnumerable<T> and void Add(T? item) { ... }, produce a new NRT warning about inconsistent surface area?

class C<T> : IEnumerable<T>
{
  ...
  public void Add(T? t) { }
}
C<object> c1 = [maybeNull]; // 2 warnings: the iteration type is `object!` but the element is maybe-null `object`, 
// and `Add` is incompatible with `IEnumerable<>`

Discussed in LDM 11/15. In short, we're going with option 1 (check conversion of elements to iteration type).