-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Collection expressions: inferred element type #8582
Open
cston
wants to merge
29
commits into
dotnet:main
Choose a base branch
from
cston:spread-conditional-more
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
5afdd36
Collection expressions: spread conditional expression
cston 6b7a720
Refactor rules
cston 81e151a
Renames
cston bf15073
Move proposal to separate doc
cston 55a9f3f
Add foreach
cston 5ef984e
Misc.
cston f317fb3
Misc.
cston adca12d
Add compile time *collection_type*
cston 8a9d513
Add natural type alternative
cston 7df30e1
Merge remote-tracking branch 'upstream/main' into spread-conditional
cston fc81418
Misc
cston 17a690f
More detail
cston 681611d
Update table
cston fafd194
Update table
cston c242f78
Updates
cston 053a2d7
Merge remote-tracking branch 'upstream/main' into spread-conditional
cston 527ed1a
Merge remote-tracking branch 'upstream/main' into spread-conditional
cston 4f64715
Natural type
cston a30070c
Rename
cston 7bad2fe
Misc.
cston d01a055
Address feedback
cston 3db2932
Misc.
cston 50684c6
Address feedback
cston 098b617
Add conversion from type question
cston d337611
IEnumerable<T> column description
cston 8a204fd
Move to working group folder
cston 7f54133
Updates
cston 777084c
Rename
cston ccee4a2
Revert change to collection-expressions.md
cston File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
137 changes: 137 additions & 0 deletions
137
...ings/working-groups/collection-literals/collection-expressions-inferred-type.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# Collection expressions: inferred type | ||
|
||
## Summary | ||
|
||
Inferring an element type for collection expressions based on the elements in the collection, and choosing a containing collection type, would allow using collection expressions in locations that are implicitly-typed. | ||
|
||
## Motivation | ||
|
||
Inferring an element type would allow collection expressions to be used in `foreach` and in spread elements. | ||
In these cases, the inferred *element type* is observable, but the containing *collection type* is not observable. | ||
|
||
```csharp | ||
foreach (var i in [x, y, z]) { } | ||
|
||
int[] items = [x, y, .. b ? [z] : []]; | ||
``` | ||
|
||
If the compiler chooses a specific containing *collection type*, collection expressions could be used in other implicitly-typed locations where the collection type is observable. | ||
|
||
```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>(T) | ||
``` | ||
|
||
## Inferred element type | ||
|
||
The *element type* `E` inferred for a collection expressions 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, where for each element `Eᵢ`: | ||
* 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 a type, there is no contribution. | ||
|
||
If there is no *best common type*, the collection expression has no type. | ||
|
||
Inferring the element type is sufficient for scenarios such as `foreach` and spreads where the *collection type* is *not observable*. For those cases, the compiler may use any conforming representation for the collection, including eliding the collection instance altogether. | ||
|
||
```csharp | ||
foreach (var i in [1, .. b ? [2, 3] : []]) { } // ok: collection of int | ||
foreach (var i in []) { } // error: cannot determine element type | ||
foreach (var i in [1, null]) { } // error: no common type for int, <null> | ||
``` | ||
|
||
## Natural collection type | ||
|
||
For implicitly-typed scenarios where the *collection type* is observable, the compiler needs to choose a specific collection type in addition to inferring the element type. | ||
|
||
The choice of collection type has a few implications: | ||
- **Mutability**: Can the collection instance or the elements be modified? | ||
- **Allocations**: How many allocations are required to create the instance? | ||
- **`IEnumerable<T>`**: Is the collection instance implicitly convertible to `IEnumerable<T>`, and perhaps other collection interfaces? | ||
- **Non-type arguments**: Does the collection type support elements that are not valid as type arguments, such as pointers or `ref struct`? | ||
- **Async code**: Can the collection be used in `async` code or an iterator? | ||
|
||
The table below includes some possible collection types, and implications for each type. | ||
|
||
|Collection type|Mutable|Allocs|`IEnumerable<T>`|Non-type args|Async|Details| | ||
|:---:|:---:|:---:|:---:|:---:|:---:|:---:| | ||
|`T[]`|elements only|1|Yes|pointers|Yes| | | ||
|`List<T>`|Yes|2|Yes|No|Yes| | | ||
|`ReadOnlySpan<T>`|No|0/1|No|No|No|stack/heap allocated buffer| | ||
|`ReadOnlyMemory<T>`|No|1|Yes|No|Yes|heap allocated buffer| | ||
|`IEnumerable<T>`|No|1+|Yes|No|Yes|context-dependent implementation| | ||
|*Anonymous type*|?|1+|Yes|Yes|Yes|compiler-generated type| | ||
|
||
## Breaking changes | ||
|
||
Previously, collection expressions [*conversions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) relied on *conversion from expression* to a target type. | ||
And previously, collection expression [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) relied on input and output type inference for the elements. | ||
For scenarios where the natural type of the collection could be used instead, there is a potential breaking change. | ||
|
||
If the natural type is `IEnumerable<T>`, the following is a breaking change due to the **conversion** from `List<int>` to `IEnumerable<int>` | ||
|
||
```csharp | ||
bool b = true; | ||
List<int> x = [1, 2, 3]; | ||
var y = b ? x : [4]; // y: previously List<int>, now IEnumerable<int> | ||
``` | ||
|
||
The following is a breaking change in overload resolution due to **type inference** from the natural type to `T`: | ||
|
||
```csharp | ||
Log([1, 2, 3]); // previously Log<T>(IEnumerable<T>), now ambiguous | ||
|
||
void Log<T>(T item) { ... } | ||
void Log<T>(IEnumerable<T> items) { ... } | ||
``` | ||
|
||
## Open questions | ||
|
||
### Support `foreach` and spread over typeless collection expressions? | ||
|
||
Should we support collection expressions in `foreach` expressions and in spread elements, even if we decide not to support a natural *collection type* in general? | ||
|
||
What are the set of locations where the collection type is not observable that should be supported? | ||
|
||
### Collection type? | ||
|
||
If we do support a natural *collection type*, which collection type should we use? See the table above for some considerations. | ||
|
||
### Conversions from type? | ||
|
||
Is the natural type considered for *conversion from type*? | ||
|
||
For instance, can a collection expression with natural type be assigned to `object`? | ||
|
||
```csharp | ||
object obj = [1, 2, 3]; // convert col<int> to object? | ||
|
||
[Value([1, 2, 3])] // convert col<int> to object? | ||
static void F() { } | ||
|
||
class ValueAttribute : Attribute | ||
{ | ||
public ValueAttribute(object value) { } | ||
} | ||
``` | ||
|
||
### Target-type `foreach` collection? | ||
|
||
Should the collection expression be target-typed when used in `foreach` with an *explicitly typed iteration variable*? | ||
|
||
In short, if the iteration variable type is explicitly typed as `E`, should we use `col<E>` as the target type of the `foreach` expression? | ||
|
||
```csharp | ||
foreach (bool? b in [false, true, null]) { } // target type: col<bool?>? | ||
foreach (byte b in [1, 2, 3]) { } // target type: col<byte>? | ||
``` | ||
|
||
### Target-type spread collection? | ||
|
||
If a spread element is contained in a *target-typed* collection expression, should the spread element expression be target-typed? | ||
|
||
In short, if the containing collection expression has a target type with *element type* `E`, should we use `col<E>` as the target type for any spread element expressions? | ||
|
||
```csharp | ||
int[] x = [1, ..[]]; // spread target type: col<int>? | ||
object[] y = [2, ..[default]]; // spread target type: col<object>? | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would a plain array of target type work? the existing workaround is
foreach (byte b in (byte[])[1,2,3]) { }
sharplab shows array is less baggyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A
ReadOnlySpan<T>
is way better (even aSpan<T>
is better)sharplab.io