Skip to content
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 literals: update type inference #7284

Merged
merged 9 commits into from
Jun 21, 2023
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 51 additions & 28 deletions proposals/collection-literals.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,46 +295,69 @@ List<string> e3 = [..e1]; // error?

## Type inference
```c#
AsArray([1, 2, 3]); // ok: AsArray<int>(int[])
[4].AsImmutableArray(); // ok: AsImmutableArray<int>(ImmutableArray<int>)
var a = AsArray([1, 2, 3]); // AsArray<int>(int[])

static T[] AsArray<T>(this T[] arg) => arg;
static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
static T[] AsArray<T>(T[] arg) => arg;
```

The existing rules for type inference (see [§11.6.3](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1163-type-inference)) include the following **additions** for *exact inferences* §11.6.3.9, *lower-bound inferences* §11.6.3.10 and *upper-bound inferences* §11.6.3.11. The additions are the same for each section.
The [*type inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1163-type-inference) rules are **updated** to include inferences from collection literal expressions.

> An *exact inference* *from* a type `U` *to* a type `V` is made as follows:
> 11.6.3.2 The first phase
>
> - If `V` is one of the *unfixed* `Xᵢ` then `U` is added to the set of exact bounds for `Xᵢ`.
> - Otherwise, sets `V₁...Vₑ` and `U₁...Uₑ` are determined by checking if any of the following cases apply:
> - `V` is an array type `V₁[...]` and `U` is an array type `U₁[...]` of the same rank
> - **`V` is a single-dimensional array type `V₁[]` and `U` is a collection literal with element type `U₁`**
> - `V` is the type `V₁?` and `U` is the type `U₁`
> - `V` is a constructed type `C<V₁...Vₑ>` and `U` is a constructed type `C<U₁...Uₑ>`
> - **`V` is a constructible collection type `C<V₁>` with element type `V₁`, and `U` is a collection literal with element type `U₁`**
>
> If any of these cases apply then an *exact inference* is made from each `Uᵢ` to the corresponding `Vᵢ`.

_How do we recognize that `C<V₁>` has element type `V₁`?_
> For each of the method arguments `Eᵢ`:
>
> - **If `Eᵢ` is a *collection literal*, a *collection element type inference* is made *from* `Eᵢ` *to* the corresponding *parameter type* `Tᵢ`.**
> - ...
>
A *collection element type inference* is made *from* a expression `E` *to* a type `T` as follows:
- If `E` is a collection literal with elements `Eᵢ`, and `T` is a *single-dimensional array type* `Tₑ[]` or `T` is [*collection type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tₑ`, then for each `Eᵢ`:
- If `Eᵢ` is a *collection literal*, a *collection element type inference* is made *from* `Eᵢ` *to* `Tₑ`.
- Otherwise, if `Eᵢ` has type `Uᵢ` then a *lower-bound inference* is made *from* `Uᵢ` *to* `Tₑ`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this say something like "for each Ei.."?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"for each Ei" is included two lines above.

Copy link
Contributor

@RikkiGibson RikkiGibson Jun 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I was thrown off by the mis-formatting from GitHub.

- Otherwise, no inferences are made for `Eᵢ`.

_Update to include inference from dictionary types._
## Extension methods
```c#
var d = ["Alice": 42, "Bob": 43].AsDictionary(comparer);
var ia = [4].AsImmutableArray(); // AsImmutableArray<int>(ImmutableArray<int>)

static Dictionary<TKey, TValue> AsDictionary<TKey, TValue>(
this List<KeyValuePair<TKey, TValue>> list,
IEqualityComparer<TKey> comparer = null) { ... }
static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
```

_What are the implications of type inference of spread elements?_
```c#
F([..[1, 2]]); // ok: F<int>(int[])
The [*extension method invocation*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11783-extension-method-invocations) rules are **updated** to include conversions from collection literal expressions.

static void F<T>(T[] arg) { }
```
> 11.7.8.3 Extension method invocations
>
> An extension method `Cᵢ.Mₑ` is *eligible* if:
>
> - ...
> - An implicit identity, reference, **collection literal**, or boxing conversion exists from *expr* to the type of the first parameter of `Mₑ`.

## Open questions

- How do we recognize that `C<V₁>` has element type `V₁`?

- Infer from nested collection literals?

```c#
NestedArray([[1, 2, 3], []]); // NestedArray<int>(int[][])

static void NestedArray<T>(T[][] arg) { }
```

This might be supported by introducing a *collection type* at compile time that represents an arbitrary collection with a specific *natural element type* and that is recognized in type inference similar to support for [*function types*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#natural-function-type) for lambda expressions.

- What changes are required for conditional operator?

- What changes are required for dictionary types?

```c#
var d = ["Alice": 42, "Bob": 43].AsDictionary(comparer);

static Dictionary<TKey, TValue> AsDictionary<TKey, TValue>(
this List<KeyValuePair<TKey, TValue>> list,
IEqualityComparer<TKey> comparer = null) { ... }
```

### Interaction with natural type
## Interaction with natural type

The *natural type* should not prevent conversions to other collection types in *best common type* or *type inference* scenarios.
```c#
Expand Down