diff --git a/proposals/csharp-12.0/collection-expressions.md b/proposals/csharp-12.0/collection-expressions.md index 60c3cc88cd..98b36cf572 100644 --- a/proposals/csharp-12.0/collection-expressions.md +++ b/proposals/csharp-12.0/collection-expressions.md @@ -103,22 +103,25 @@ Collection literals are [target-typed](https://github.com/dotnet/csharplang/blob A *collection expression conversion* allows a collection expression to be converted to a type. An implicit *collection expression conversion* exists from a collection expression to the following types: -* A single dimensional *array type* `T[]` +* A single dimensional *array type* `T[]`, in which case the *element type* is `T` * A *span type*: * `System.Span` - * `System.ReadOnlySpan` -* A *type* with a *[create method](#create-methods)* with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) determined from a `GetEnumerator` instance method or enumerable interface, not from an extension method + * `System.ReadOnlySpan` + in which cases the *element type* is `T` +* A *type* with an appropriate *[create method](#create-methods)* and a corresponding *element type* resulting from that determination * A *struct* or *class type* that implements `System.Collections.IEnumerable` where: - * The *type* has an *[applicable](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11642-applicable-function-member)* constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression. - * If the collection expression has any elements, the *type* has an *[applicable](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11642-applicable-function-member)* instance or extension method `Add` that can be invoked with a single argument of the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement), and the method is accessible at the location of the collection expression. + * The *type* has an *[applicable](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11642-applicable-function-member)* constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression, and + * If the collection expression has any elements, the *type* has an *[applicable](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11642-applicable-function-member)* instance or extension method `Add` that can be invoked with a single argument of the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement), and the method is accessible at the location of the collection expression, + in which case the *element type* is the *iteration type* of the *type*. * An *interface type*: * `System.Collections.Generic.IEnumerable` * `System.Collections.Generic.IReadOnlyCollection` * `System.Collections.Generic.IReadOnlyList` * `System.Collections.Generic.ICollection` - * `System.Collections.Generic.IList` + * `System.Collections.Generic.IList` + in which cases the *element type* is `T` -The implicit conversion exists if the type has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `U` where for each *element* `Eᵢ` in the collection expression: +The implicit conversion exists if the type has an *element type* `U` where for each *element* `Eᵢ` in the collection expression: * If `Eᵢ` is an *expression element*, there is an implicit conversion from `Eᵢ` to `U`. * If `Eᵢ` is an *spread element* `Sᵢ`, there is an implicit conversion from the *iteration type* of `Sᵢ` to `U`. @@ -158,21 +161,33 @@ namespace System.Runtime.CompilerServices The attribute can be applied to a `class`, `struct`, `ref struct`, or `interface`. The attribute is not inherited although the attribute can be applied to a base `class` or an `abstract class`. -The collection type must have an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement). +The *builder type* must be a non-generic `class` or `struct`. -For the *create method*: +First, the set of applicable *create methods* `CM` is determined. +It consists of methods that meet the following requirements: -* The *builder type* must be a non-generic `class` or `struct`. +* The method must have the name specified in the `[CollectionBuilder(...)]` attribute. * The method must be defined on the *builder type* directly. * The method must be `static`. * The method must be accessible where the collection expression is used. * The *arity* of the method must match the *arity* of the collection type. -* The method must have a single parameter of type `System.ReadOnlySpan`, passed by value, and there is an [*identity conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1022-identity-conversion) from `E` to the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of the *collection type*. +* The method must have a single parameter of type `System.ReadOnlySpan`, passed by value. * There is an [*identity conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1022-identity-conversion), [*implicit reference conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1028-implicit-reference-conversions), or [*boxing conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1029-boxing-conversions) from the method return type to the *collection type*. -An error is reported if the `[CollectionBuilder]` attribute does not refer to an invocable method with the expected signature. +Methods declared on base types or interfaces are ignored and not part of the `CM` set. -Method overloads on the *builder type* with distinct signatures are ignored. Methods declared on base types or interfaces are ignored. +If the `CM` set is empty, then the *collection type* doesn't have an *element type* and doesn't have a *create method*. None of the following steps apply. + +Second, an attempt is made to determine the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of the *collection type* from a `GetEnumerator` instance method or enumerable interface, not from an extension method. + +If an *iteration type* can be determined, then the *element type* of the *collection type* is the *iteration type*. If only one method among those in the `CM` set has an [*identity conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1022-identity-conversion) from `E` to the *element type* of the *collection type*, that is the *create method* for the *collection type*. Otherwise, the *collection type* doesn't have a *create method*. None of the following steps apply. + +Third (ie. if an *iteration type* cannot be determined), an attempt is made to infer the *element type*. +If the `CM` set contains more than one method, the inference fails and the *collection type* doesn't have an *element type* and doesn't have a *create method*. + +Otherwise, type `E1` is determined by substituting type parameters of the only method from the `CM` set (`M`) with corresponding *collection type* type parameters in its `E`. If any generic constraints are violated for `E1`, the *collection type* doesn't have an *element type* and doesn't have a *create method*. Otherwise, `E1` is the *element type* and `M` is the *create method* for the *collection type*. + +An error is reported if the `[CollectionBuilder]` attribute does not refer to an invokable method with the expected signature. For a *collection expression* with a target type C<S0, S1, …> where the *type declaration* C<T0, T1, …> has an associated *builder method* B.M<U0, U1, …>(), the *generic type arguments* from the target type are applied in order — and from outermost containing type to innermost — to the *builder method*. @@ -377,7 +392,7 @@ The existing rules for the [*first phase*](https://github.com/dotnet/csharpstand > > An *input type inference* is made *from* an expression `E` *to* a type `T` in the following way: > -> * If `E` is a *collection expression* with elements `Eᵢ`, and `T` is a type with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` or `T` is a *nullable value type* `T0?` and `T0` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ`, then for each `Eᵢ`: +> * If `E` is a *collection expression* with elements `Eᵢ`, and `T` is a type with an *element type* `Tₑ` or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `Tₑ`, then for each `Eᵢ`: > * If `Eᵢ` is an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. > * If `Eᵢ` is an *spread element* with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Sᵢ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Sᵢ` *to* `Tₑ`. > * *[existing rules from first phase]* ... @@ -386,7 +401,7 @@ The existing rules for the [*first phase*](https://github.com/dotnet/csharpstand > > An *output type inference* is made *from* an expression `E` *to* a type `T` in the following way: > -> * If `E` is a *collection expression* with elements `Eᵢ`, and `T` is a type with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` or `T` is a *nullable value type* `T0?` and `T0` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ`, then for each `Eᵢ`: +> * If `E` is a *collection expression* with elements `Eᵢ`, and `T` is a type with an *element type* `Tₑ` or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `Tₑ`, then for each `Eᵢ`: > * If `Eᵢ` is an *expression element*, then an *output type inference* is made *from* `Eᵢ` *to* `Tₑ`. > * If `Eᵢ` is an *spread element*, no inference is made from `Eᵢ`. > * *[existing rules from output type inferences]* ... @@ -437,7 +452,7 @@ In the updated rules: > > * **`E` is a *collection expression* and one of the following holds:** > * **`T₁` is `System.ReadOnlySpan`, and `T₂` is `System.Span`, and an implicit conversion exists from `E₁` to `E₂`** -> * **`T₁` is `System.ReadOnlySpan` or `System.Span`, and `T₂` is an *array_or_array_interface* with *iteration type* `E₂`, and an implicit conversion exists from `E₁` to `E₂`** +> * **`T₁` is `System.ReadOnlySpan` or `System.Span`, and `T₂` is an *array_or_array_interface* with *element type* `E₂`, and an implicit conversion exists from `E₁` to `E₂`** > * **`T₁` is not a *span_type*, and `T₂` is not a *span_type*, and an implicit conversion exists from `T₁` to `T₂`** > * **`E` is not a *collection expression* and one of the following holds:** > * `E` exactly matches `T₁` and `E` does not exactly match `T₂` @@ -830,6 +845,28 @@ However, given the breadth and consistency brought by the new literal syntax, we ## Unresolved questions [unresolved]: #unresolved-questions +* Should we allow inferring the *element type* when the *iteration type* is "ambiguous" (by some definition)? +For example: +```csharp +Collection x = [1L, 2L]; + +// error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable'; try casting to a specific interface instantiation +foreach (var x in new Collection) { } + +static class Builder +{ + public Collection Create(ReadOnlySpan items) => throw null; +} + +[CollectionBuilder(...)] +class Collection : IEnumerable, IEnumerable +{ + IEnumerator IEnumerable.GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; +} +``` + * Should it be legal to create and immediately index into a collection literal? Note: this requires an answer to the unresolved question below of whether collection literals have a *natural type*. * Stack allocations for huge collections might blow the stack. Should the compiler have a heuristic for placing this data on the heap? Should the language be unspecified to allow for this flexibility? We should follow the spec for [`params Span`](https://github.com/dotnet/csharplang/issues/1757). * Do we need to target-type `spread_element`? Consider, for example: diff --git a/proposals/params-collections.md b/proposals/params-collections.md index 3b4e503c71..3229ecc2cd 100644 --- a/proposals/params-collections.md +++ b/proposals/params-collections.md @@ -47,28 +47,30 @@ A *parameter_collection* consists of an optional set of *attributes*, a `params` a *type*, and an *identifier*. A parameter collection declares a single parameter of the given type with the given name. The *type* of a parameter collection shall be one of the following valid target types for a collection expression (see https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions): -- A single dimensional *array type* `T[]` +- A single dimensional *array type* `T[]`, in which case the *element type* is `T` - A *span type* - `System.Span` - - `System.ReadOnlySpan` -- A *type* with a *[create method](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#create-methods)*, - which is at least as accessible as the declaring member, and with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/statements.md#1395-the-foreach-statement) - determined from a `GetEnumerator` instance method or enumerable interface, not from an extension method. + - `System.ReadOnlySpan` + in which cases the *element type* is `T` +- A *type* with an appropriate *[create method](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#create-methods)*, + which is at least as accessible as the declaring member, and with a corresponding *element type* resulting from that determination - A *struct* or *class type* that implements `System.Collections.IEnumerable` where: - - The *type* has a constructor that can be invoked with no arguments, and the constructor is at least as accessible as the declaring member. + - The *type* has a constructor that can be invoked with no arguments, and the constructor is at least as accessible as the declaring member, and - The *type* has an instance (not an extension) method `Add` that can be invoked with a single argument of the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/statements.md#1395-the-foreach-statement), - and the method is at least as accessible as the declaring member. + and the method is at least as accessible as the declaring member, + in which case the *element type* is the *iteration type* - An *interface type* - `System.Collections.Generic.IEnumerable`, - `System.Collections.Generic.IReadOnlyCollection`, - `System.Collections.Generic.IReadOnlyList`, - `System.Collections.Generic.ICollection`, - - `System.Collections.Generic.IList` + - `System.Collections.Generic.IList` + in which cases the *element type* is `T` In a method invocation, a parameter collection permits either a single argument of the given parameter type to be specified, or -it permits zero or more arguments of the collection [iteration type](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/statements.md#1395-the-foreach-statement) -to be specified. Parameter collections are described further in *[Parameter collections](#parameter-collections)*. +it permits zero or more arguments of the collection's *element type* to be specified. +Parameter collections are described further in *[Parameter collections](#parameter-collections)*. A *parameter_collection* may occur after an optional parameter, but cannot have a default value – the omission of arguments for a *parameter_collection* would instead result in the creation of an empty collection. @@ -87,7 +89,7 @@ A parameter collection permits arguments to be specified in one of two ways in a - The argument given for a parameter collection can be a single expression that is implicitly convertible to the parameter collection type. In this case, the parameter collection acts precisely like a value parameter. - Alternatively, the invocation can specify zero or more arguments for the parameter collection, where each argument is an expression - that is implicitly convertible to the parameter collection [iteration type](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/statements.md#1395-the-foreach-statement). + that is implicitly convertible to the parameter collection's *element type*. In this case, the invocation creates an instance of the parameter collection type according to the rules specified in [Collection expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md) as though the arguments were used as expression elements in a collection expression in the same order, @@ -117,7 +119,7 @@ The [Applicable function member](https://github.com/dotnet/csharpstandard/blob/d If a function member that includes a parameter collection is not applicable in its normal form, the function member might instead be applicable in its ***expanded form***: - The expanded form is constructed by replacing the parameter collection in the function member declaration with - zero or more value parameters of the parameter collection [iteration type](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/statements.md#1395-the-foreach-statement) + zero or more value parameters of the parameter collection's *element type* such that the number of arguments in the argument list `A` matches the total number of parameters. If `A` has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable. @@ -158,7 +160,7 @@ In case the parameter type sequences `{P₁, P₂, ..., Pᵥ}` and `{Q₁, Q₂, - **params collection of `Mᵢ` is `System.ReadOnlySpan`, and params collection of `Mₑ` is `System.Span`, and an implicit conversion exists from `Eᵢ` to `Eₑ`** - **params collection of `Mᵢ` is `System.ReadOnlySpan` or `System.Span`, and params collection of `Mₑ` is an *[array_or_array_interface__type](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution)* - with *[iteration type](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/statements.md#1395-the-foreach-statement)* `Eₑ`, and an implicit conversion exists from `Eᵢ` to `Eₑ`** + with *element type* `Eₑ`, and an implicit conversion exists from `Eᵢ` to `Eₑ`** - **both params collections are not *span_type*s, and an implicit conversion exists from params collection of `Mᵢ` to params collection of `Mₑ`** - Otherwise, no function member is better.