# C# Language Design Meeting for January 10th, 2024

## Agenda

- [Collection expressions: conversion vs construction](#collection-expressions-conversion-vs-construction)

## Quote of the Day

- Nothing particularly amusing was said today

## Discussion

### Collection expressions: conversion vs construction

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:

* Only allow `Add` methods that return `void` or `bool`.
    * This would mean that older collection types like `ImmutableArray<T>` version 7.0 wouldn't be considered convertible.
* Only forbid `Add` methods that return the type itself, like `ImmutableArray<T>.Add` does.
    * This doesn't solve the v7.0 problem from the above problem.
    * It may still exclude valid types that have a fluent calling style.
* What if we made the existence of a conversion only look for an accessible `Add`, and then further restrict "creatability" further down the line?
    * This would us to keep overload resolution stable for older APIs, and then let them upgrade to `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 `string`s 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.

#### Conclusions

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.
