From 5afdd36598ee9770906f102e646916ed56fddd22 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:43:17 -0800 Subject: [PATCH 01/14] Collection expressions: spread conditional expression --- .../csharp-12.0/collection-expressions.md | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/proposals/csharp-12.0/collection-expressions.md b/proposals/csharp-12.0/collection-expressions.md index eaec64fc0e..7f8a78f446 100644 --- a/proposals/csharp-12.0/collection-expressions.md +++ b/proposals/csharp-12.0/collection-expressions.md @@ -118,9 +118,16 @@ An implicit *collection expression conversion* exists from a collection expressi * `System.Collections.Generic.ICollection` * `System.Collections.Generic.IList` -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: -* 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`. +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` and for each *element* `Eᵢ` in the collection expression one of the following holds: +* `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `U`. +* `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `U`. + +An expression `E` is *spreadable* as values of type `U` if one of the following holds: +* `E` is a *collection expression* and for each element `Eᵢ` in the collection expression there is an implicit conversion to `U`. +* `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, and `x` and `y` are *spreadable* as values of type `U`. +* `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Uₑ` and there is an implicit conversion from `Uₑ` to `U`. + +*Should `switch` expressions be supported in spread elements?* There is no *collection expression conversion* from a collection expression to a multi dimensional *array type*. @@ -228,6 +235,8 @@ If the target type is a *struct* or *class type* that implements `System.Collect * For each element in order: * If the element is an *expression element*, the applicable `Add` instance or extension method is invoked with the element *expression* as the argument. (Unlike classic [*collection initializer behavior*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#117154-collection-initializers), element evaluation and `Add` calls are not necessarily interleaved.) * If the element is a *spread element* then one of the following is used: + * If the spread element expression is a *collection expression*, then the collection expression elements are evaluated in order as if the elements were in the containing collection expression. + * If the spread element expression is a *conditional expression*, then the condition is evaluated and if `true` the second-, otherwise the third-operand is evaluated as if the operand was an element in the containing collection expression. * An applicable `GetEnumerator` instance or extension method is invoked on the *spread element expression* and for each item from the enumerator the applicable `Add` instance or extension method is invoked on the *collection instance* with the item as the argument. If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions. * An applicable `AddRange` instance or extension method is invoked on the *collection instance* with the spread element *expression* as the argument. * An applicable `CopyTo` instance or extension method is invoked on the *spread element expression* with the collection instance and `int` index as arguments. @@ -250,6 +259,8 @@ If the target type is an *array*, a *span*, a type with a *[create method](#crea * For each element in order: * If the element is an *expression element*, the initialization instance *indexer* is invoked to add the evaluated expression at the current index. * If the element is a *spread element* then one of the following is used: + * If the spread element expression is a *collection expression*, then the collection expression elements are evaluated in order as if the elements were in the containing collection expression. + * If the spread element expression is a *conditional expression*, then the condition is evaluated and if `true` the second-, otherwise the third-operand is evaluated as if the operand was an element in the containing collection expression. * A member of a well-known interface or type is invoked to copy items from the spread element expression to the initialization instance. * An applicable `GetEnumerator` instance or extension method is invoked on the *spread element expression* and for each item from the enumerator, the initialization instance *indexer* is invoked to add the item at the current index. If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions. * An applicable `CopyTo` instance or extension method is invoked on the *spread element expression* with the initialization instance and `int` index as arguments. @@ -379,8 +390,15 @@ The existing rules for the [*first phase*](https://github.com/dotnet/csharpstand > > * 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 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ₑ`. +> * If `Eᵢ` is a *spread element* `..s`, then a *spread element inference* is made *from* `s` *to* `Tₑ`. > * *[existing rules from first phase]* ... +> +> A *spread element inference* is made *from* an expression `E` to a collection expression *iteration type* `T` as follows: +> * If `E` is a *collection expression* with elements `Eᵢ`, 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* `..s`, then an *spread element inference* is made *from* `s` *to* `Tₑ`. +> * If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then an *spread element inference* is made *from* `x` *to* `Tₑ` and an *spread element inference* is made *from* `y` *to* `Tₑ`. +> * If `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Eₑ` *to* `Tₑ`. > 11.6.3.7 Output type inferences > From 6b7a7203a2af26edbb7487192b6765ba1d220e39 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:58:41 -0800 Subject: [PATCH 02/14] Refactor rules --- .../csharp-12.0/collection-expressions.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/proposals/csharp-12.0/collection-expressions.md b/proposals/csharp-12.0/collection-expressions.md index 7f8a78f446..7d1b6449a5 100644 --- a/proposals/csharp-12.0/collection-expressions.md +++ b/proposals/csharp-12.0/collection-expressions.md @@ -388,16 +388,16 @@ 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 an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. -> * If `Eᵢ` is a *spread element* `..s`, then a *spread element inference* is made *from* `s` *to* `Tₑ`. +> * 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ᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. > * *[existing rules from first phase]* ... > -> A *spread element inference* is made *from* an expression `E` to a collection expression *iteration type* `T` as follows: -> * If `E` is a *collection expression* with elements `Eᵢ`, 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* `..s`, then an *spread element inference* is made *from* `s` *to* `Tₑ`. -> * If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then an *spread element inference* is made *from* `x` *to* `Tₑ` and an *spread element inference* is made *from* `y` *to* `Tₑ`. +> A *collection element inference* is made *from* a collection expression element `Eᵢ` *to* an *iteration type* `Tₑ` as follows: +> * If `Eᵢ` is an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * If `Eᵢ` is a *spread element* `..s`, then a *spread element inference* is made *from* `s` *to* `Tₑ`. +> +> A *spread element inference* is made *from* an expression `E` to a collection expression *iteration type* `Tₑ` as follows: +> * If `E` is a *collection expression* with elements `Eᵢ`, then for each `Eᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then a *spread element inference* is made *from* `x` *to* `Tₑ` and a *spread element inference* is made *from* `y` *to* `Tₑ`. > * If `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Eₑ` *to* `Tₑ`. > 11.6.3.7 Output type inferences @@ -406,7 +406,7 @@ The existing rules for the [*first phase*](https://github.com/dotnet/csharpstand > > * 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 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ᵢ`. +> * If `Eᵢ` is a *spread element*, no inference is made from `Eᵢ`. > * *[existing rules from output type inferences]* ... ## Extension methods From 81e151a31703046e0cb7d867e88e4f0e2416767a Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:06:01 -0800 Subject: [PATCH 03/14] Renames --- proposals/csharp-12.0/collection-expressions.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/proposals/csharp-12.0/collection-expressions.md b/proposals/csharp-12.0/collection-expressions.md index 7d1b6449a5..fc425e6ef4 100644 --- a/proposals/csharp-12.0/collection-expressions.md +++ b/proposals/csharp-12.0/collection-expressions.md @@ -118,14 +118,14 @@ An implicit *collection expression conversion* exists from a collection expressi * `System.Collections.Generic.ICollection` * `System.Collections.Generic.IList` -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` and for each *element* `Eᵢ` in the collection expression one of the following holds: -* `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `U`. -* `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `U`. - -An expression `E` is *spreadable* as values of type `U` if one of the following holds: -* `E` is a *collection expression* and for each element `Eᵢ` in the collection expression there is an implicit conversion to `U`. -* `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, and `x` and `y` are *spreadable* as values of type `U`. -* `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Uₑ` and there is an implicit conversion from `Uₑ` to `U`. +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) `Tₑ` and for each *element* `Eᵢ` in the collection expression one of the following holds: +* `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. +* `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`. + +An expression `E` is *spreadable* as values of type `Tₑ` if one of the following holds: +* `E` is a *collection expression* and for each element `Eᵢ` in the collection expression there is an implicit conversion to `Tₑ`. +* `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, and `x` and `y` are *spreadable* as values of type `Tₑ`. +* `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ` and there is an implicit conversion from `Eₑ` to `Tₑ`. *Should `switch` expressions be supported in spread elements?* From bf15073e4601093c562e9ca2e7282655b89c2557 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:30:36 -0800 Subject: [PATCH 04/14] Move proposal to separate doc --- proposals/collection-expressions-inline.md | 50 +++++++++++++++++++ .../csharp-12.0/collection-expressions.md | 26 ++-------- 2 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 proposals/collection-expressions-inline.md diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md new file mode 100644 index 0000000000..2695849620 --- /dev/null +++ b/proposals/collection-expressions-inline.md @@ -0,0 +1,50 @@ +# Collection expressions: inline collections + +## Summary + +Support collection expressions *inline* within spread elements and `foreach`. + +## Detailed design + +The following changes are made to the corresponding sections of the [*collection expressions proposal*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md). + +### Conversions + +The implicit *collection expression conversion* exists if the type has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` and for each *element* `Eᵢ` in the collection expression one of the following holds: +* `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. +* `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`. + +An expression `E` is *spreadable* as values of type `Tₑ` if one of the following holds: +* `E` is a *collection expression* and for each element `Eᵢ` in the collection expression there is an implicit conversion to `Tₑ`. +* `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, and `x` and `y` are *spreadable* as values of type `Tₑ`. +* `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ` and there is an implicit conversion from `Eₑ` to `Tₑ`. + +*Should `switch` expressions be supported in spread elements?* + +### Construction + +For construction of a *collection expression*, ... + +* For each element in order: + * If the element is an *expression element* ... + * If the element is a *spread element* then one of the following is used: + * If the spread element expression is a *collection expression*, then the collection expression elements are evaluated in order as if the elements were in the containing collection expression. + * If the spread element expression is a *conditional expression*, then the condition is evaluated and if `true` the second-, otherwise the third-operand is evaluated as if the operand was an element in the containing collection expression. + * ... + +### Type inference + +> 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ᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * *[existing rules from first phase]* ... +> +> A *collection element inference* is made *from* a collection expression element `Eᵢ` *to* an *iteration type* `Tₑ` as follows: +> * If `Eᵢ` is an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * If `Eᵢ` is a *spread element* `..s`, then a *spread element inference* is made *from* `s` *to* `Tₑ`. +> +> A *spread element inference* is made *from* an expression `E` to a collection expression *iteration type* `Tₑ` as follows: +> * If `E` is a *collection expression* with elements `Eᵢ`, then for each `Eᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then a *spread element inference* is made *from* `x` *to* `Tₑ` and a *spread element inference* is made *from* `y` *to* `Tₑ`. +> * If `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Eₑ` *to* `Tₑ`. + diff --git a/proposals/csharp-12.0/collection-expressions.md b/proposals/csharp-12.0/collection-expressions.md index fc425e6ef4..ee29de5551 100644 --- a/proposals/csharp-12.0/collection-expressions.md +++ b/proposals/csharp-12.0/collection-expressions.md @@ -120,14 +120,7 @@ An implicit *collection expression conversion* exists from a collection expressi 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) `Tₑ` and for each *element* `Eᵢ` in the collection expression one of the following holds: * `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. -* `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`. - -An expression `E` is *spreadable* as values of type `Tₑ` if one of the following holds: -* `E` is a *collection expression* and for each element `Eᵢ` in the collection expression there is an implicit conversion to `Tₑ`. -* `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, and `x` and `y` are *spreadable* as values of type `Tₑ`. -* `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ` and there is an implicit conversion from `Eₑ` to `Tₑ`. - -*Should `switch` expressions be supported in spread elements?* +* `Eᵢ` is a *spread element* with *iteration type* `Sᵢ` and there is an implicit conversion from `Sᵢ` to `Tₑ`. There is no *collection expression conversion* from a collection expression to a multi dimensional *array type*. @@ -235,8 +228,6 @@ If the target type is a *struct* or *class type* that implements `System.Collect * For each element in order: * If the element is an *expression element*, the applicable `Add` instance or extension method is invoked with the element *expression* as the argument. (Unlike classic [*collection initializer behavior*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#117154-collection-initializers), element evaluation and `Add` calls are not necessarily interleaved.) * If the element is a *spread element* then one of the following is used: - * If the spread element expression is a *collection expression*, then the collection expression elements are evaluated in order as if the elements were in the containing collection expression. - * If the spread element expression is a *conditional expression*, then the condition is evaluated and if `true` the second-, otherwise the third-operand is evaluated as if the operand was an element in the containing collection expression. * An applicable `GetEnumerator` instance or extension method is invoked on the *spread element expression* and for each item from the enumerator the applicable `Add` instance or extension method is invoked on the *collection instance* with the item as the argument. If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions. * An applicable `AddRange` instance or extension method is invoked on the *collection instance* with the spread element *expression* as the argument. * An applicable `CopyTo` instance or extension method is invoked on the *spread element expression* with the collection instance and `int` index as arguments. @@ -259,8 +250,6 @@ If the target type is an *array*, a *span*, a type with a *[create method](#crea * For each element in order: * If the element is an *expression element*, the initialization instance *indexer* is invoked to add the evaluated expression at the current index. * If the element is a *spread element* then one of the following is used: - * If the spread element expression is a *collection expression*, then the collection expression elements are evaluated in order as if the elements were in the containing collection expression. - * If the spread element expression is a *conditional expression*, then the condition is evaluated and if `true` the second-, otherwise the third-operand is evaluated as if the operand was an element in the containing collection expression. * A member of a well-known interface or type is invoked to copy items from the spread element expression to the initialization instance. * An applicable `GetEnumerator` instance or extension method is invoked on the *spread element expression* and for each item from the enumerator, the initialization instance *indexer* is invoked to add the item at the current index. If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions. * An applicable `CopyTo` instance or extension method is invoked on the *spread element expression* with the initialization instance and `int` index as arguments. @@ -388,17 +377,10 @@ 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ᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * 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 an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * If `Eᵢ` is a *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]* ... -> -> A *collection element inference* is made *from* a collection expression element `Eᵢ` *to* an *iteration type* `Tₑ` as follows: -> * If `Eᵢ` is an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. -> * If `Eᵢ` is a *spread element* `..s`, then a *spread element inference* is made *from* `s` *to* `Tₑ`. -> -> A *spread element inference* is made *from* an expression `E` to a collection expression *iteration type* `Tₑ` as follows: -> * If `E` is a *collection expression* with elements `Eᵢ`, then for each `Eᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. -> * If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then a *spread element inference* is made *from* `x` *to* `Tₑ` and a *spread element inference* is made *from* `y` *to* `Tₑ`. -> * If `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Eₑ` *to* `Tₑ`. > 11.6.3.7 Output type inferences > From 55a9f3f29b62eb0ec2ea8a6e134808923e1ce827 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sun, 28 Jan 2024 14:01:16 -0800 Subject: [PATCH 05/14] Add foreach --- proposals/collection-expressions-inline.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index 2695849620..93e2609991 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -48,3 +48,22 @@ For construction of a *collection expression*, ... > * If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then a *spread element inference* is made *from* `x` *to* `Tₑ` and a *spread element inference* is made *from* `y` *to* `Tₑ`. > * If `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Eₑ` *to* `Tₑ`. +### Foreach + +A *collection expression* may be used as the collection in a `foreach` statement. + +If the `foreach` statement has an *explicitly typed iteration variable* of type `Tₑ`, the compiler verifies the following for each element `Eᵢ` and reports an error otherwise: +* `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. +* `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`. + +*The verification above is exactly the requirement for *conversions*. We should re-use the verification statement rather than restating here.* + +If the `foreach` statement has an *implicitly typed iteration variable*, the type of the *iteration variable* is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the collection expression *elements*. If there is no best common type, an error is reported. + +*State the rules for *best common type* using the recursive iteration of elements and any nested collection expressions.* + +*The [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) is defined using *output type inference* from each expression. Do we need to update *output type inference* to infer from spread elements that contain nested collection expressions? Why didn't we need inference from spread elements previously without nested collection expressions?* + +For a collection expression used as the collection in a `foreach` statement, the compiler may use any conforming representation for the collection instance, including eliding the collection. + +*What are the order of operations? Particularly, what do we guarantee with respect to evaluating the loop body before evaluating subsequent elements?* \ No newline at end of file From 5ef984e5333447ff13d942cb6e80425e0163c0a1 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:38:03 -0800 Subject: [PATCH 06/14] Misc. --- proposals/collection-expressions-inline.md | 54 ++++++++++++++-------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index 93e2609991..e28f7e4b87 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -1,39 +1,55 @@ -# Collection expressions: inline collections +# Collection expressions: spread inline collections ## Summary -Support collection expressions *inline* within spread elements and `foreach`. +Support collection expressions *inline* within spread elements. + +## Motivation + +Allow collection expressions to contain conditional elements by using spreads of *conditional expressions with nested collection expressions*. ## Detailed design -The following changes are made to the corresponding sections of the [*collection expressions proposal*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md). +To support spreads of conditional expressions with nested collection expressions, the following changes are made to the [*collection expressions specification*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md). ### Conversions -The implicit *collection expression conversion* exists if the type has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` and for each *element* `Eᵢ` in the collection expression one of the following holds: -* `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. -* `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`. +For an implicit *collection expression conversion* to a target type, the values yielded from any *spread elements* must be implicitly convertible to the *iteration type* of the target type. + +The [*conversions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) section is updated as follows: -An expression `E` is *spreadable* as values of type `Tₑ` if one of the following holds: -* `E` is a *collection expression* and for each element `Eᵢ` in the collection expression there is an implicit conversion to `Tₑ`. -* `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, and `x` and `y` are *spreadable* as values of type `Tₑ`. -* `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ` and there is an implicit conversion from `Eₑ` to `Tₑ`. +> The implicit *collection expression conversion* exists if the type has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` and for each *element* `Eᵢ` in the collection expression one of the following holds: +> * `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. +> * **`Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`.** +> +> **An expression `E` is *spreadable* as values of type `Tₑ` if one of the following holds:** +> * **`E` is a *collection expression* and for each element `Eᵢ` in the collection expression there is an implicit conversion to `Tₑ`.** +> * **`E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, and `x` and `y` are *spreadable* as values of type `Tₑ`.** +> * `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ` and there is an implicit conversion from `Eₑ` to `Tₑ`. *Should `switch` expressions be supported in spread elements?* ### Construction -For construction of a *collection expression*, ... +The elements of a nested collection expression within a spread can be added to the containing collection instance without requiring an intermediate collection. -* For each element in order: - * If the element is an *expression element* ... - * If the element is a *spread element* then one of the following is used: - * If the spread element expression is a *collection expression*, then the collection expression elements are evaluated in order as if the elements were in the containing collection expression. - * If the spread element expression is a *conditional expression*, then the condition is evaluated and if `true` the second-, otherwise the third-operand is evaluated as if the operand was an element in the containing collection expression. - * ... +The [*construction*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#construction) section is updated as follows: + +> * For each element in order: +> * If the element is an *expression element* ... +> * If the element is a *spread element* then one of the following is used: +> * **If the spread element expression is a *collection expression*, then the collection expression elements are evaluated in order as if the elements were in the containing collection expression.** +> * **If the spread element expression is a *conditional expression*, then the condition is evaluated and if `true` the second-, otherwise the third-operand is evaluated as if the operand was an element in the containing collection expression.** +> * ... + +*Is there any implied order of evaluation of the elements in the nested collection expression with respect to the following elements in the containing collection expression?* ### Type inference +Type inference sees through spreads, conditional expressions, and nested collection expressions to make inferences from the elements within the nested collection expressions. + +The [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) section is updated as follows, with some refactoring in addition to **new rules**: + > 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ᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. @@ -44,8 +60,8 @@ For construction of a *collection expression*, ... > * If `Eᵢ` is a *spread element* `..s`, then a *spread element inference* is made *from* `s` *to* `Tₑ`. > > A *spread element inference* is made *from* an expression `E` to a collection expression *iteration type* `Tₑ` as follows: -> * If `E` is a *collection expression* with elements `Eᵢ`, then for each `Eᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. -> * If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then a *spread element inference* is made *from* `x` *to* `Tₑ` and a *spread element inference* is made *from* `y` *to* `Tₑ`. +> * **If `E` is a *collection expression* with elements `Eᵢ`, then for each `Eᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`.** +> * **If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then a *spread element inference* is made *from* `x` *to* `Tₑ` and a *spread element inference* is made *from* `y` *to* `Tₑ`.** > * If `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Eₑ` *to* `Tₑ`. ### Foreach From f317fb388daeb32f1100263793532ceea110f720 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:15:29 -0800 Subject: [PATCH 07/14] Misc. --- proposals/collection-expressions-inline.md | 31 ++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index e28f7e4b87..977d36dc25 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -1,16 +1,25 @@ -# Collection expressions: spread inline collections +# Collection expressions: inline collections ## Summary -Support collection expressions *inline* within spread elements. +Support collection expressions *inline* in expression contexts where the collection type is not important or not observable. ## Motivation -Allow collection expressions to contain conditional elements by using spreads of *conditional expressions with nested collection expressions*. +Collection expressions could be used with spreads to allow adding elements *conditionally* to the containing collection: +```csharp +int[] array = [x, y, .. b ? [z] : []]; +``` -## Detailed design +Collection expressions could be used directly in `foreach`: +```csharp +foreach (bool value in [false, true]) { } +``` +In these cases, the *collection type* of the inline collection expression is unspecified and the choice of how or whether to instantiate the collection is left to the compiler. -To support spreads of conditional expressions with nested collection expressions, the following changes are made to the [*collection expressions specification*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md). +## Spreads with collection expressions + +To support spreads of conditional expressions with nested collection expressions, the following changes are made to the [*spec*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md). ### Conversions @@ -64,16 +73,16 @@ The [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/ > * **If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then a *spread element inference* is made *from* `x` *to* `Tₑ` and a *spread element inference* is made *from* `y` *to* `Tₑ`.** > * If `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Eₑ` *to* `Tₑ`. -### Foreach +*The type of the nested collection expression in a conditional expression may be distinct depending on whether the conditional expression is in a spread element or outside a collection expression.* + +## Foreach -A *collection expression* may be used as the collection in a `foreach` statement. +The collection in a `foreach` statement may be a *collection expression*. -If the `foreach` statement has an *explicitly typed iteration variable* of type `Tₑ`, the compiler verifies the following for each element `Eᵢ` and reports an error otherwise: +If the `foreach` statement has an *explicitly typed iteration variable* of type `Tₑ`, the compiler verifies the following for each element `Eᵢ` in the collection expression and reports an error otherwise: * `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. * `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`. -*The verification above is exactly the requirement for *conversions*. We should re-use the verification statement rather than restating here.* - If the `foreach` statement has an *implicitly typed iteration variable*, the type of the *iteration variable* is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the collection expression *elements*. If there is no best common type, an error is reported. *State the rules for *best common type* using the recursive iteration of elements and any nested collection expressions.* @@ -82,4 +91,4 @@ If the `foreach` statement has an *implicitly typed iteration variable*, the typ For a collection expression used as the collection in a `foreach` statement, the compiler may use any conforming representation for the collection instance, including eliding the collection. -*What are the order of operations? Particularly, what do we guarantee with respect to evaluating the loop body before evaluating subsequent elements?* \ No newline at end of file +*What are the order of operations? Particularly, what do we guarantee with respect to evaluating the loop body before evaluating subsequent elements?* From adca12d4e8495a6915e9a6a29029e2b61091122e Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sat, 3 Feb 2024 09:43:18 -0800 Subject: [PATCH 08/14] Add compile time *collection_type* --- proposals/collection-expressions-inline.md | 61 ++++++++----------- .../csharp-12.0/collection-expressions.md | 8 +-- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index 977d36dc25..6fbd0981e5 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -17,26 +17,29 @@ foreach (bool value in [false, true]) { } ``` In these cases, the *collection type* of the inline collection expression is unspecified and the choice of how or whether to instantiate the collection is left to the compiler. -## Spreads with collection expressions - -To support spreads of conditional expressions with nested collection expressions, the following changes are made to the [*spec*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md). +## Detailed design ### Conversions -For an implicit *collection expression conversion* to a target type, the values yielded from any *spread elements* must be implicitly convertible to the *iteration type* of the target type. +A *collection_type* is introduced to represent an enumerable type with a specific element type and an *unspecified* collection type. +A *collection_type* with element type `E` is referred to here as *`col`*. -The [*conversions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) section is updated as follows: +A *collection_type* exists at compile time only; a *collection_type* cannot be referenced in source or metadata. -> The implicit *collection expression conversion* exists if the type has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` and for each *element* `Eᵢ` in the collection expression one of the following holds: -> * `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. -> * **`Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`.** -> -> **An expression `E` is *spreadable* as values of type `Tₑ` if one of the following holds:** -> * **`E` is a *collection expression* and for each element `Eᵢ` in the collection expression there is an implicit conversion to `Tₑ`.** -> * **`E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, and `x` and `y` are *spreadable* as values of type `Tₑ`.** -> * `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ` and there is an implicit conversion from `Eₑ` to `Tₑ`. +The [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of a *collection_type* `col` is `E`. + +An implicit *collection_type conversion* exists from a type with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` to the *collection_type* `col`. -*Should `switch` expressions be supported in spread elements?* +The collection expression [*conversions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) section is updated as follows: + +> An implicit *collection expression conversion* exists from a collection expression to the following types: +> * **A *collection_type* `col`** +> * A single dimensional *array type* `T[]` +> * ... +> +> The implicit *collection expression conversion* exists if the type has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` where for each *element* `Eᵢ` in the collection expression: +> * If `Eᵢ` is an *expression element*, there is an implicit conversion from `Eᵢ` to `Tₑ`. +> * If `Eᵢ` is a *spread element* `..Sᵢ`, there is an implicit conversion **from `Sᵢ` to the *collection_type* `col`**. ### Construction @@ -55,40 +58,28 @@ The [*construction*](https://github.com/dotnet/csharplang/blob/main/proposals/cs ### Type inference -Type inference sees through spreads, conditional expressions, and nested collection expressions to make inferences from the elements within the nested collection expressions. +No changes are made to the [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) section. +Since *type inference* from a spread element relies on the *iteration type* of the spread element expression, and since collection expressions do not have a *type* or an *iteration type*, there is no type inference from a collection expression nested within a spread element. -The [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) section is updated as follows, with some refactoring in addition to **new rules**: +The relevant part of the [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) section is included here for reference: > 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ᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * 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 an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. +> * If `Eᵢ` is a *spread element* `..Sᵢ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ` *to* `Tₑ`. > * *[existing rules from first phase]* ... -> -> A *collection element inference* is made *from* a collection expression element `Eᵢ` *to* an *iteration type* `Tₑ` as follows: -> * If `Eᵢ` is an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. -> * If `Eᵢ` is a *spread element* `..s`, then a *spread element inference* is made *from* `s` *to* `Tₑ`. -> -> A *spread element inference* is made *from* an expression `E` to a collection expression *iteration type* `Tₑ` as follows: -> * **If `E` is a *collection expression* with elements `Eᵢ`, then for each `Eᵢ` a *collection element inference* is made *from* `Eᵢ` *to* `Tₑ`.** -> * **If `E` is a [*conditional expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) `b ? x : y`, then a *spread element inference* is made *from* `x` *to* `Tₑ` and a *spread element inference* is made *from* `y` *to* `Tₑ`.** -> * If `E` has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Eₑ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Eₑ` *to* `Tₑ`. - -*The type of the nested collection expression in a conditional expression may be distinct depending on whether the conditional expression is in a spread element or outside a collection expression.* -## Foreach +### Foreach The collection in a `foreach` statement may be a *collection expression*. If the `foreach` statement has an *explicitly typed iteration variable* of type `Tₑ`, the compiler verifies the following for each element `Eᵢ` in the collection expression and reports an error otherwise: -* `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. -* `Eᵢ` is a *spread element* `..s` and `s` is *spreadable* as values of type `Tₑ`. +* If `Eᵢ` is an *expression element*, there is an implicit conversion from `Eᵢ` to `Tₑ`. +* If `Eᵢ` is a *spread element* `..Sᵢ`, there is an implicit conversion from `Sᵢ` to the *collection_type* `col`. If the `foreach` statement has an *implicitly typed iteration variable*, the type of the *iteration variable* is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the collection expression *elements*. If there is no best common type, an error is reported. -*State the rules for *best common type* using the recursive iteration of elements and any nested collection expressions.* - -*The [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) is defined using *output type inference* from each expression. Do we need to update *output type inference* to infer from spread elements that contain nested collection expressions? Why didn't we need inference from spread elements previously without nested collection expressions?* - For a collection expression used as the collection in a `foreach` statement, the compiler may use any conforming representation for the collection instance, including eliding the collection. *What are the order of operations? Particularly, what do we guarantee with respect to evaluating the loop body before evaluating subsequent elements?* diff --git a/proposals/csharp-12.0/collection-expressions.md b/proposals/csharp-12.0/collection-expressions.md index ee29de5551..5d9f8c1f66 100644 --- a/proposals/csharp-12.0/collection-expressions.md +++ b/proposals/csharp-12.0/collection-expressions.md @@ -118,9 +118,9 @@ An implicit *collection expression conversion* exists from a collection expressi * `System.Collections.Generic.ICollection` * `System.Collections.Generic.IList` -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) `Tₑ` and for each *element* `Eᵢ` in the collection expression one of the following holds: -* `Eᵢ` is an *expression element* and there is an implicit conversion from `Eᵢ` to `Tₑ`. -* `Eᵢ` is a *spread element* with *iteration type* `Sᵢ` and there is an implicit conversion from `Sᵢ` to `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) `Tₑ` where for each *element* `Eᵢ` in the collection expression: +* If `Eᵢ` is an *expression element*, there is an implicit conversion from `Eᵢ` to `Tₑ`. +* If `Eᵢ` is a *spread element* `..Sᵢ`, there is an implicit conversion from the *iteration type* of `Sᵢ` to `Tₑ`. There is no *collection expression conversion* from a collection expression to a multi dimensional *array type*. @@ -379,7 +379,7 @@ The existing rules for the [*first phase*](https://github.com/dotnet/csharpstand > > * 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 an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. -> * If `Eᵢ` is a *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ₑ`. +> * If `Eᵢ` is a *spread element* `..Sᵢ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ` *to* `Tₑ`. > * *[existing rules from first phase]* ... > 11.6.3.7 Output type inferences From 8a9d513a0517458835e661aca4be2722530b6301 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sat, 3 Feb 2024 16:42:55 -0800 Subject: [PATCH 09/14] Add natural type alternative --- proposals/collection-expressions-inline.md | 61 ++++++++++++++-------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index 6fbd0981e5..5f843f2ddb 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -2,26 +2,28 @@ ## Summary -Support collection expressions *inline* in expression contexts where the collection type is not important or not observable. +Support collection expressions *inline* in expression contexts where the *collection type* is not observable. ## Motivation -Collection expressions could be used with spreads to allow adding elements *conditionally* to the containing collection: +Inline collection expressions could be used with spreads to allow adding elements *conditionally* to the containing collection: ```csharp -int[] array = [x, y, .. b ? [z] : []]; +int[] items = [x, y, .. b ? [z] : []]; ``` -Collection expressions could be used directly in `foreach`: +Inline collection expressions could be used directly in `foreach`: ```csharp -foreach (bool value in [false, true]) { } +foreach (var b in [false, true]) { } ``` -In these cases, the *collection type* of the inline collection expression is unspecified and the choice of how or whether to instantiate the collection is left to the compiler. +In these cases, the *collection type* of the inline collection expression is unspecified. The choice of how or whether to instantiate the collection is left to the compiler. ## Detailed design ### Conversions -A *collection_type* is introduced to represent an enumerable type with a specific element type and an *unspecified* collection type. +To support conversions for inline collection expressions, an abstract *collection_type* is introduced. + +A *collection_type* represents an enumerable type with a specific element type and an *unspecified* collection type. A *collection_type* with element type `E` is referred to here as *`col`*. A *collection_type* exists at compile time only; a *collection_type* cannot be referenced in source or metadata. @@ -43,22 +45,13 @@ The collection expression [*conversions*](https://github.com/dotnet/csharplang/b ### Construction -The elements of a nested collection expression within a spread can be added to the containing collection instance without requiring an intermediate collection. - -The [*construction*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#construction) section is updated as follows: +The elements of a nested collection expression within a spread can be added to the containing collection instance directly, without instantiating an intermediate collection. -> * For each element in order: -> * If the element is an *expression element* ... -> * If the element is a *spread element* then one of the following is used: -> * **If the spread element expression is a *collection expression*, then the collection expression elements are evaluated in order as if the elements were in the containing collection expression.** -> * **If the spread element expression is a *conditional expression*, then the condition is evaluated and if `true` the second-, otherwise the third-operand is evaluated as if the operand was an element in the containing collection expression.** -> * ... - -*Is there any implied order of evaluation of the elements in the nested collection expression with respect to the following elements in the containing collection expression?* +The elements of the nested collection expression are evaluated in order within the nested collection. There is no implied evaluation order between the elements of the nested collection and other elements within the containing collection expression. ### Type inference -No changes are made to the [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) section. +No changes are made for [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference). Since *type inference* from a spread element relies on the *iteration type* of the spread element expression, and since collection expressions do not have a *type* or an *iteration type*, there is no type inference from a collection expression nested within a spread element. The relevant part of the [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) section is included here for reference: @@ -78,8 +71,34 @@ If the `foreach` statement has an *explicitly typed iteration variable* of type * If `Eᵢ` is an *expression element*, there is an implicit conversion from `Eᵢ` to `Tₑ`. * If `Eᵢ` is a *spread element* `..Sᵢ`, there is an implicit conversion from `Sᵢ` to the *collection_type* `col`. -If the `foreach` statement has an *implicitly typed iteration variable*, the type of the *iteration variable* is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the collection expression *elements*. If there is no best common type, an error is reported. +If the `foreach` statement has an *implicitly typed iteration variable*, the type of the *iteration variable* is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the types of the elements. If there is no *best common type*, an error is reported. + +For each element `Eᵢ` in the collection expression, the type contributing to the *best common type* is the following if any: +* If `Eᵢ` is an *expression element*, the type of `Eᵢ`. +* If `Eᵢ` is a *spread element* `..Sᵢ`, the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ`. + +Since collection expressions do not have a *type* or an *iteration type*, the *best common type* does not consider elements from nested collection expressions. For a collection expression used as the collection in a `foreach` statement, the compiler may use any conforming representation for the collection instance, including eliding the collection. -*What are the order of operations? Particularly, what do we guarantee with respect to evaluating the loop body before evaluating subsequent elements?* +The elements of the collection are evaluated in order, and the loop body is executed for each element in order. The compiler *may* evaluate subsequent elements before executing the loop body for preceding elements. + +## Alternatives + +### Natural type + +If collection expressions have a *natural type*, that will allow use of collection expressions in cases where there is no target type: +```csharp +var a = [1, 2, 3]; // var +var b = [x, y].Where(e => e != null); // extension methods +var c = Identity([x, y]); // type inference + +static T Identity(T t) => t; +``` + +Natural type would also allow a subset of the scenarios that are supported above. But since the above proposal relies on target typing and natural type relies on *best common type* of the elements within the collection expression, there will be some limitations if we don't also support target typing with nested collection expressions. +```csharp +byte[] x = [1, .. b ? [2] : []]; // error: cannot convert int to byte +int[] y = [1, .. b ? [default] : []]; // error: no type for [default] +int?[] z = [1, .. b ? [2, null] : []]; // error: no common type for [2, null] +``` From fc81418d1d4fbb587ccffdc9c5d60663e111842a Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sat, 3 Feb 2024 20:20:18 -0800 Subject: [PATCH 10/14] Misc --- proposals/collection-expressions-inline.md | 2 +- proposals/csharp-12.0/collection-expressions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index 5f843f2ddb..e19ed84eb9 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -73,7 +73,7 @@ If the `foreach` statement has an *explicitly typed iteration variable* of type If the `foreach` statement has an *implicitly typed iteration variable*, the type of the *iteration variable* is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the types of the elements. If there is no *best common type*, an error is reported. -For each element `Eᵢ` in the collection expression, the type contributing to the *best common type* is the following if any: +For each element `Eᵢ` in the collection expression, the type if any contributing to the *best common type* is the following: * If `Eᵢ` is an *expression element*, the type of `Eᵢ`. * If `Eᵢ` is a *spread element* `..Sᵢ`, the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ`. diff --git a/proposals/csharp-12.0/collection-expressions.md b/proposals/csharp-12.0/collection-expressions.md index 10c7814bf3..09e8952dab 100644 --- a/proposals/csharp-12.0/collection-expressions.md +++ b/proposals/csharp-12.0/collection-expressions.md @@ -379,7 +379,7 @@ The existing rules for the [*first phase*](https://github.com/dotnet/csharpstand > > * 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 an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. -> * If `Eᵢ` is a *spread element* `..Sᵢ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ` *to* `Tₑ`. +> * If `Eᵢ` is a *spread element* `..Sᵢ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ` *to* `Tₑ`. > * *[existing rules from first phase]* ... > 11.6.3.7 Output type inferences From 17a690f044692b6494e6ff03ea3ff968bd6b4ece Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sat, 3 Feb 2024 20:23:40 -0800 Subject: [PATCH 11/14] More detail --- proposals/collection-expressions-inline.md | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index e19ed84eb9..36d9ed63ef 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -87,7 +87,20 @@ The elements of the collection are evaluated in order, and the loop body is exec ### Natural type -If collection expressions have a *natural type*, that will allow use of collection expressions in cases where there is no target type: +Add collection expression *natural type*. + +The natural type would be a combination of the *best common type* for the element type (see [foreach](#foreach) above), and a choice of collection type, such as one of the following: + +|Collection type|Mutable|Allocations|Async code|Returnable +|:---:|:---:|:---:|:---:|:---:| +|T[]|Items only|1|Yes|Yes| +|List<T>|Yes|2|Yes|Yes| +|Span<T>|Items only|0*|No|No*| +|ReadOnlySpan<T>|No|0*|No|No*| +|Memory<T>|Items only|0*|Yes|No*| +|ReadOnlyMemory<T>|No|0*|Yes|No*| + +Natural type would allow use of collection expressions in cases where there is no target type: ```csharp var a = [1, 2, 3]; // var var b = [x, y].Where(e => e != null); // extension methods @@ -96,9 +109,16 @@ var c = Identity([x, y]); // type inference static T Identity(T t) => t; ``` +Natural type would likely only apply when there is a *best common type* for the elements. +```csharp +var d = []; // error +var e = [default]; // error: no type for default +var f = [1, null]; // error: no common type for int and +``` + Natural type would also allow a subset of the scenarios that are supported above. But since the above proposal relies on target typing and natural type relies on *best common type* of the elements within the collection expression, there will be some limitations if we don't also support target typing with nested collection expressions. ```csharp byte[] x = [1, .. b ? [2] : []]; // error: cannot convert int to byte int[] y = [1, .. b ? [default] : []]; // error: no type for [default] -int?[] z = [1, .. b ? [2, null] : []]; // error: no common type for [2, null] +int?[] z = [1, .. b ? [2, null] : []]; // error: no common type for int and ``` From 681611d1ed47a9ba1692a8eea93fab6ecab7ef1e Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sun, 4 Feb 2024 10:04:44 -0800 Subject: [PATCH 12/14] Update table --- proposals/collection-expressions-inline.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index 36d9ed63ef..6c39c3d44c 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -95,10 +95,10 @@ The natural type would be a combination of the *best common type* for the elemen |:---:|:---:|:---:|:---:|:---:| |T[]|Items only|1|Yes|Yes| |List<T>|Yes|2|Yes|Yes| -|Span<T>|Items only|0*|No|No*| -|ReadOnlySpan<T>|No|0*|No|No*| -|Memory<T>|Items only|0*|Yes|No*| -|ReadOnlyMemory<T>|No|0*|Yes|No*| +|Span<T>|Items only|0/1|No|No/Yes| +|ReadOnlySpan<T>|No|0/1|No|No/Yes| +|Memory<T>|Items only|0/1|Yes|No/Yes| +|ReadOnlyMemory<T>|No|0/1|Yes|No/Yes| Natural type would allow use of collection expressions in cases where there is no target type: ```csharp From fafd1944d087e37104d242885535e5206694b710 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Sun, 4 Feb 2024 10:56:55 -0800 Subject: [PATCH 13/14] Update table --- proposals/collection-expressions-inline.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index 6c39c3d44c..ffca1dff7a 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -97,8 +97,8 @@ The natural type would be a combination of the *best common type* for the elemen |List<T>|Yes|2|Yes|Yes| |Span<T>|Items only|0/1|No|No/Yes| |ReadOnlySpan<T>|No|0/1|No|No/Yes| -|Memory<T>|Items only|0/1|Yes|No/Yes| -|ReadOnlyMemory<T>|No|0/1|Yes|No/Yes| +|Memory<T>|Items only|1|Yes|Yes| +|ReadOnlyMemory<T>|No|1|Yes|Yes| Natural type would allow use of collection expressions in cases where there is no target type: ```csharp From c242f78f6851c835d0d1248ebc4dee721832543f Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:35:34 -0800 Subject: [PATCH 14/14] Updates --- proposals/collection-expressions-inline.md | 145 +++++++++++---------- 1 file changed, 79 insertions(+), 66 deletions(-) diff --git a/proposals/collection-expressions-inline.md b/proposals/collection-expressions-inline.md index ffca1dff7a..7e94450844 100644 --- a/proposals/collection-expressions-inline.md +++ b/proposals/collection-expressions-inline.md @@ -2,7 +2,7 @@ ## Summary -Support collection expressions *inline* in expression contexts where the *collection type* is not observable. +Support collection expressions *inline* in expression contexts where the *collection type* is unspecified. ## Motivation @@ -13,83 +13,119 @@ int[] items = [x, y, .. b ? [z] : []]; Inline collection expressions could be used directly in `foreach`: ```csharp -foreach (var b in [false, true]) { } +foreach (bool b in [false, true]) { } ``` -In these cases, the *collection type* of the inline collection expression is unspecified. The choice of how or whether to instantiate the collection is left to the compiler. +In these cases, the *collection type* of the inline collection expression is unspecified. The choice of how and whether to instantiate the collection is left to the compiler. -## Detailed design +## Compile time *collection_type* +A *collection_type* is introduced that represents an enumerable type with a specific element type and an *unspecified* containing collection type. -### Conversions - -To support conversions for inline collection expressions, an abstract *collection_type* is introduced. - -A *collection_type* represents an enumerable type with a specific element type and an *unspecified* collection type. A *collection_type* with element type `E` is referred to here as *`col`*. -A *collection_type* exists at compile time only; a *collection_type* cannot be referenced in source or metadata. +A *collection_type* exists at compile time only — a *collection_type* cannot be referenced in source or metadata. The [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of a *collection_type* `col` is `E`. -An implicit *collection_type conversion* exists from a type with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` to the *collection_type* `col`. +A *collection_type* may be used as a target type or as a natural type for collection expressions. -The collection expression [*conversions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) section is updated as follows: +## Target types + +A *collection_type* can be used as a target type for collection expressions in spreads and `foreach`. For these scenarios, it is the target *iteration type* that is important; the target collection type is not observable. + +### Conversions +[target-type-conversions]: #target-type-conversions + +To enable a *collection_type* as a target type, a [*collection expression conversion*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) is defined for *collection_type*: > An implicit *collection expression conversion* exists from a collection expression to the following types: > * **A *collection_type* `col`** > * A single dimensional *array type* `T[]` > * ... > -> The implicit *collection expression conversion* exists if the type has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ` where for each *element* `Eᵢ` in the collection expression: +> 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) `Tₑ` where for each *element* `Eᵢ` in the collection expression: > * If `Eᵢ` is an *expression element*, there is an implicit conversion from `Eᵢ` to `Tₑ`. -> * If `Eᵢ` is a *spread element* `..Sᵢ`, there is an implicit conversion **from `Sᵢ` to the *collection_type* `col`**. +> * If `Eᵢ` is a *spread element* `..Sᵢ`, there is an implicit conversion from the *iteration type* of `Sᵢ` to `Tₑ`. -### Construction +### Spreads +[target-type-spreads]: #target-type-spreads -The elements of a nested collection expression within a spread can be added to the containing collection instance directly, without instantiating an intermediate collection. +If a spread element `..S` is contained in a collection expression with a target type with *iteration type* `E`, then the target type of the *expression* `S` is `col`. +The target type is only used when the spread element expression does not have a type. +If the target type is used, an error is reported if the spread element expression is not implicitly convertible to `col`. -The elements of the nested collection expression are evaluated in order within the nested collection. There is no implied evaluation order between the elements of the nested collection and other elements within the containing collection expression. +For a collection expression within a spread, the compiler may use any conforming representation for the nested collection instance, including eliding the collection instance and instead adding the elements to the containing collection instance directly. -### Type inference +The elements of a collection expression within a spread are evaluated in order as if the spread elements were declared in the containing collection expression directly. -No changes are made for [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference). -Since *type inference* from a spread element relies on the *iteration type* of the spread element expression, and since collection expressions do not have a *type* or an *iteration type*, there is no type inference from a collection expression nested within a spread element. +### Foreach +[target-type-foreach]: #target-type-foreach -The relevant part of the [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) section is included here for reference: +If a `foreach` statement has an *explicitly typed iteration variable* of type `E`, the target type of the `foreach` collection is `col`. +The target type is only used when the collection does not have a type. +If the target type is used, an error is reported if the collection is not implicitly convertible to `col`. -> 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 an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. -> * If `Eᵢ` is a *spread element* `..Sᵢ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ` *to* `Tₑ`. -> * *[existing rules from first phase]* ... +For a collection expression in a `foreach` collection, the compiler may use any conforming representation for the collection instance, including eliding the collection instance. -### Foreach +The elements of a collection expression in a `foreach` are evaluated in order, and the loop body is executed for each element in order. The compiler *may* evaluate subsequent elements before executing the loop body for preceding elements. -The collection in a `foreach` statement may be a *collection expression*. +## Natural type -If the `foreach` statement has an *explicitly typed iteration variable* of type `Tₑ`, the compiler verifies the following for each element `Eᵢ` in the collection expression and reports an error otherwise: -* If `Eᵢ` is an *expression element*, there is an implicit conversion from `Eᵢ` to `Tₑ`. -* If `Eᵢ` is a *spread element* `..Sᵢ`, there is an implicit conversion from `Sᵢ` to the *collection_type* `col`. +A *collection_type* can be used as a natural type for collection expressions in scenarios where only the *iteration type* of the collection expression is used, and where the containing collection is not observable. -If the `foreach` statement has an *implicitly typed iteration variable*, the type of the *iteration variable* is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the types of the elements. If there is no *best common type*, an error is reported. +The *natural type* of a collection expression is the *collection_type* `col` where `E` is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the elements. +For each element `Eᵢ` in the collection expression, the type contributed to the *best common type* is the following: +* If `Eᵢ` is an *expression element*, the contribution is the *type* of `Eᵢ`. If `Eᵢ` does not have a type, there is no contribution. +* If `Eᵢ` is a *spread element* `..Sᵢ`, the contribution is the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ`. If `Sᵢ` does not have an *iteration type*, there is no contribution. -For each element `Eᵢ` in the collection expression, the type if any contributing to the *best common type* is the following: -* If `Eᵢ` is an *expression element*, the type of `Eᵢ`. -* If `Eᵢ` is a *spread element* `..Sᵢ`, the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ`. +If there is no *best common type* of the elements, the collection expression has no natural type. + +Natural type is not a replacement for target type — there are scenarios supported by target type but not natural type. That said, we could choose to support either target type or natural type, or both. +```csharp +// conditional expression: +// - target type col: ok +// - natural type col: error: cannot convert int to byte +byte[] a = [1, 2, .. b ? [3] : []]; +``` + +### Conversions +[natural-type-conversions]: #natural-type-conversions + +To allow *collection_types* to be used in *best common type* — for example `b ? [x] : [y]` — a conversion is defined between *collection_types*: + +> An implicit *collection_type conversion* exists from the *collection_type* `col` to the *collection_type* `col` if there is an implicit conversion from `T` to `U`. + +### Foreach +[natural-type-foreach]: #natural-type-foreach -Since collection expressions do not have a *type* or an *iteration type*, the *best common type* does not consider elements from nested collection expressions. +The primary scenario for natural type may be `foreach`. -For a collection expression used as the collection in a `foreach` statement, the compiler may use any conforming representation for the collection instance, including eliding the collection. +If the `foreach` statement has an *implicitly typed iteration variable*, the type of the *iteration variable* is the *iteration type* of the collection. If the iteration type of the collection cannot be determined, an error is reported. +```csharp +foreach (var i in [1, 2, 3]) { } // ok: col +foreach (var i in []) { } // error: cannot determine type +foreach (var i in [1, null]) { } // error: no common type for int, +``` -The elements of the collection are evaluated in order, and the loop body is executed for each element in order. The compiler *may* evaluate subsequent elements before executing the loop body for preceding elements. +The `foreach` scenario could be extended to include conditional expressions, switch expressions, and spreads within collection expressions: +```csharp +foreach (var i in b ? [x] : [y, z]) { } +foreach (var i in b switch { true => [x], false => [y, z]}) { } +foreach (var i in [x, .. b ? [y, z] : []]) { } +``` -## Alternatives +## Observable collection instances -### Natural type +The discussion of natural type above covers scenarios where the collection instance is not observable. +We could go further and support scenarios where the collection type is unspecified but where the choice of collection type is observable. +For those cases, the compiler will need to instantiate a concrete collection type. -Add collection expression *natural type*. +```csharp +var a = [x, y]; // var +var b = [x, y].Where(e => e != null); // extension methods +var c = Identity([x, y]); // type inference: T Identity(T) +``` -The natural type would be a combination of the *best common type* for the element type (see [foreach](#foreach) above), and a choice of collection type, such as one of the following: +The following are several potential collection types: |Collection type|Mutable|Allocations|Async code|Returnable |:---:|:---:|:---:|:---:|:---:| @@ -99,26 +135,3 @@ The natural type would be a combination of the *best common type* for the elemen |ReadOnlySpan<T>|No|0/1|No|No/Yes| |Memory<T>|Items only|1|Yes|Yes| |ReadOnlyMemory<T>|No|1|Yes|Yes| - -Natural type would allow use of collection expressions in cases where there is no target type: -```csharp -var a = [1, 2, 3]; // var -var b = [x, y].Where(e => e != null); // extension methods -var c = Identity([x, y]); // type inference - -static T Identity(T t) => t; -``` - -Natural type would likely only apply when there is a *best common type* for the elements. -```csharp -var d = []; // error -var e = [default]; // error: no type for default -var f = [1, null]; // error: no common type for int and -``` - -Natural type would also allow a subset of the scenarios that are supported above. But since the above proposal relies on target typing and natural type relies on *best common type* of the elements within the collection expression, there will be some limitations if we don't also support target typing with nested collection expressions. -```csharp -byte[] x = [1, .. b ? [2] : []]; // error: cannot convert int to byte -int[] y = [1, .. b ? [default] : []]; // error: no type for [default] -int?[] z = [1, .. b ? [2, null] : []]; // error: no common type for int and -```