meetings/2024/LDM-2024-01-10.md
https://github.com/dotnet/csharplang/issues/5354
https://github.com/dotnet/csharplang/blob/d7bbc5456e51bf29ece89112a2bb10153e98a524/proposals/csharp-12.0/collection-expressions.md#should-collection-expression-conversion-require-availability-of-a-minimal-set-of-apis-for-construction
Today we looked at another scenario in collection expressions brought up by our work on params improvements. This time, we considered scenarios
that would be an error when used as params, but due to how collection expressions were specified, cannot be considered an error at definition, only
at usage; basically any type that implements IEnumerable or IEnumerable<T> is valid to be used as a params type, even if it cannot be constructed
in any real scenario. This means that, rather than the method author getting an error that their params parameter is invalid, the end user gets a worse
error about being unable to construct the parameter type. To address that, we are looking at two potential changes: require that the type has an accessible
constructor, and an accessible Add method that can be invoked with the iteration type of the collection expression. We would then further restrict
params to require that these are as accessible as the method itself, to ensure that if something is defined as params, it can be used as params when
seen.
We do, however, have some concerns, particularly around API evolution. There's a tension here between what we want to be considered convertible to collection
expressions, and what we want to prevent. A prime example is System.Collection.ImmutableArray<T>; this type has always supported collection initializers
at compile-time, because it has an Add method, but it then blows up at runtime because Add doesn't actually mutate the underlying array, it instead
returns a new ImmutableArray, and produces a NullReferenceException when doing so on a new ImmutableArray<T>(). We could try to prevent the compiler
from recognizing invalid Add methods, but we do still want to recognize the older version of ImmutableArray<T> for collection expressions. By doing
so, we ensure that overload resolution gives good errors (ImmutableArray<T> isn't constructible), rather than giving errors that no applicable methods
could be found; worse, not recognizing older versions of ImmutableArray<T> as valid conversion targets for collection expressions could then mean that
upgrading System.Collections.Immutable potentially causes a source-breaking change in overload resolution. The point is somewhat moot for ImmutableArray<T>,
as it has already been upgraded and the compiler has taken a bit of liberty with older versions to mark them as bad at compile-time, but we have no guarantees
that other community-created types that have similar construction foibles have indeed upgraded to use CollectionBuilderAttribute. We debated a few different
possible restrictions:
Add methods that return void or bool.
ImmutableArray<T> version 7.0 wouldn't be considered convertible.Add methods that return the type itself, like ImmutableArray<T>.Add does.
Add, and then further restrict "creatability" further down the line?
CollectionBuilderAttribute in new versions without
potentially introducing a source-breaking change.We distilled an important characteristic from these discussions: we don't think that IEnumerable<T>, by itself, is enough of a signal to make a type
be a creatable collection type. Instead, what we're looking for is a set of restrictions that signal the intent of the type author that users should be able
to construct the collection type. For CollectionBuilderAttribute, that is enough of a signal by itself. However, we need a similar rule for IEnumerable<T>,
and we think the existing signal for collection initializers serves as a good, well-established signal. Given this, we re-examined our handling of string.
We intentionally left string as a valid conversion target for collection expressions, but under the newly proposed rules it would not be. After some more
thinking, we're fine with this. In particular, we think that, even if we were to implement support for collection expressions to create strings in the future,
we'd need to deprioritize it in overload resolution compared to char[]. Given that, it doesn't make sense to hold a place for it, and we'll let the new rules
mark it as not a valid conversion target.
Finally today, we looked at additional restrictions for params parameters, on top of what we considered today. In particular, we want to make sure that, when
a user declares a method with a params parameter, you don't have to have a specific using in order to use it in that format. To that end, we will require
that the accessible Add method for params be an instance member, rather than an extension method.
We accept the proposed rules for collection expressions:
For a struct or class type that implements System.Collections.IEnumerable and that does not have a create method conversions section should require presence of at least the following APIs:
- An accessible constructor that is applicable with no arguments.
- An accessible Add instance or extension method that can be invoked with value of iteration type as the argument.
We will not save a spot for string.
For params parameters, we additionally require that the Add method be an instance method.