From 7fa0382b80361d422e0fa5834fe6d07774944bf5 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 26 May 2023 12:22:11 -0700 Subject: [PATCH] Collection literals: updates for dictionaries (#7214) Co-authored-by: Joseph Musser Co-authored-by: CyrusNajmabadi --- proposals/collection-literals.md | 127 +++++++++++++------------------ 1 file changed, 52 insertions(+), 75 deletions(-) diff --git a/proposals/collection-literals.md b/proposals/collection-literals.md index fe00b87f..542e107a 100644 --- a/proposals/collection-literals.md +++ b/proposals/collection-literals.md @@ -117,21 +117,18 @@ Collection literals are [target-typed](https://github.com/dotnet/csharplang/blob The following are _constructible collection types_ that can be used to construct collection literals. These are the valid _target types_ for collection literals. + +The list is in priority order. If a collection type belongs in multiple categories, the first is used. + Actual translation of the literal to the corresponding type is defined [below](#collection-literal-translation). * Single dimensional arrays (e.g. `T[]`) * [*Span types*](#span-types) * Types with a suitable [`Construct` method](#construct-methods) -* Types that support [*collection initializers*](#collection-initializers) -* Interface types `I` implemented by `List` (e.g. `IEnumerable`, `IList`, `IReadOnlyList`) - -```c# -int[] a = []; // array -Span b = [1, 2]; // span -ImmutableArray c = [3]; // Construct method -List d = [5, 6]; // collection initializer type -IEnumerable e = []; // list interface -``` +* Types that implement `System.Collections.IDictionary` - *dictionary [collection initializers](#collection-initializers)* +* Interface types _`I`_ implemented by `System.Collections.Generic.Dictionary` (e.g. `IDictionary`, `IReadOnlyDictionary`) +* Types that implement `System.Collections.IEnumerable` - [*collection initializers*](#collection-initializers) +* Interface types _`I`_ implemented by `System.Collections.Generic.List` (e.g. `IEnumerable`, `IList`, `IReadOnlyList`) ## `Construct` methods [construct-methods]: #construct-methods @@ -151,6 +148,8 @@ _This means an extension method could be added that would silently change how co Through the use of the [`init`](#init-methods) modifier, existing APIs can directly support collection literals in a manner that allows for no-overhead production of the data the final collection will store. +_Does this support construction of custom dictionary types?_ + ### `init Construct` methods [init-methods]: #init-methods @@ -218,15 +217,8 @@ Through the use of the [`init`](#init-methods) modifier, existing APIs can direc In the absence of a *constructible collection target type*, a non-empty literal can have a *natural type*. -The *natural type* is determined using 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) algorithm. - -A *natural element type* `T` is first determined from the *best common type* of: -* The *type* of each element `e_n`, and -* The *iteration type* of each element `..s_n`. - -If a *natural element type* `T` cannot be determined, the literal has no *natural type*. If `T` can be determined and it is some `KeyValuePair`, then the *natural type* of the collection is `Dictionary` (see [*dictionaries*](#dictionaries)); otherwise, the *natural type* of the collection is `List`. - -This means there is no way for a literal to have a *natural type* of some `List>` (though it certainly can be *target-typed* to that type). +The *natural type* is determined from the [*natural element type*](#natural-element-type). +If the *natural element type* `T` cannot be determined, the literal has no *natural type*. If `T` can be determined, the *natural type* of the collection is `List`. The choice of `List` rather than `T[]` or `ImmutableArray` is to allow mutation of `var` locals after initialization. `List` is preferred over `Span` because `Span` cannot be used in `async` methods. @@ -235,6 +227,12 @@ var values = [1, 2, 3]; values.Add(4); // ok ``` +The *natural element type* may be inferred from `spread_element` enumerated element type. + +```c# +var c = [..[1, 2, 3]]; // List +``` + Should `IEnumerable` contribute an *iteration type* of `object` or no contribution? ```c# @@ -287,6 +285,8 @@ The existing rules for type inference (see [§11.6.3](https://github.com/dotnet/ _How do we recognize that `C` is a constructible collection with element type `V₁`?_ +_Update to include inference from dictionary types._ + ### Interaction with natural type The *natural type* should not prevent conversions to other collection types in *best common type* or *type inference* scenarios. @@ -348,9 +348,9 @@ Span __result = __array; _Include text from [*collection initializers*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#117154-collection-initializers)_. -The *collection initializer type* must support binding to `Add(arg)` where `arg` is a single argument. +Non-dictionary construction uses `Add()` instance methods or extension methods. -`List` is *collection initializer type* and is therefore a [*constructible collection type*](#constructible-collection-types). +Dictionary construction uses indexer instance properties rather than `Add()` methods to ensure consistent _overwrite semantics_ rather than _add semantics_. ## Collection literal translation [collection-literal-translation]: #collection-literal-translation @@ -374,17 +374,7 @@ If they all have such a property, the literal is considered to have a *known len * Evaluation of the element expressions happens entirely first. Only after all those evaluations happen are calls to `Count` (or `Length` or `TryGetNonEnumeratedCount`) and all enumerations made. -* Certain translations below attempt to find a suitable `Add` method by which to add either `expression_element` or `spread_element` members to the collection. If such an `Add` method cannot be found *and* the value being added is some `KeyValuePair<,>` `__kvp`, then the translation will instead try to emit `__result[__kvp.Key] = __kvp.Value;`. - -* All methods/properties utilized in a translation (for example `Add`, `this[...]`, `Length`, `Count`, etc.) do not have to be the same. For example, `SomeCollection x = [a, b];` may invoke different `SomeCollection.Add` methods for each element in the collection literal. - - +* All methods/properties utilized in a translation (for example `Add`, `this[...]`, `Length`, `Count`, etc.) do not have to be the same. For example, `SomeCollection x = [a, b];` may invoke different `SomeCollection.Add` methods for each element in the collection literal. ### Interface translation [interface-translation]: #interface-translation @@ -420,7 +410,7 @@ Having a *known length* allows for efficient construction of a result with the p Not having a *known length* does not prevent any result from being created. However, it may result in extra CPU and memory costs producing the data, then moving to the final destination. -* For a *known length* literal `[e1, k1: v1, ..s1, e2, k2: v2, ..s2, etc]`, the translation first starts with the following: +* For a *known length* literal `[e1, k1: v1, ..s1, etc]`, the translation first starts with the following: ```c# int __len = count_of_expression_elements + @@ -451,11 +441,13 @@ Not having a *known length* does not prevent any result from being created. Howe * If `T` is some `Span`, then the literal is translated as the same as above, except that the `__result` initialization is translated as: ```c# - Span __result = stackalloc T1[__len]; + Span __result = new T1[__len]; // same assignments as the array translation ``` + The translation may use `stackalloc T1[]` rather than `new T1[]` if [*span-safety*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md) is maintained. + * If `T` is some `ReadOnlySpan`, then the literal is translated the same as for the `Span` case except that the final result will be that `Span` [implicitly converted](https://learn.microsoft.com/en-us/dotnet/api/system.span-1.op_implicit#system-span-1-op-implicit(system-span((-0)))-system-readonlyspan((-0))) to a `ReadOnlySpan`. The above forms (for arrays and spans) are the base representations of the literal value and are used for the following translation rules. @@ -478,7 +470,7 @@ Not having a *known length* does not prevent any result from being created. Howe T __result = new T(capacity: __len); __result.Add(__e1); - __result[__k1] = __v1; + __result.Add(new T1(__k1, __v1)); foreach (var __t in __s1) __result.Add(__t); @@ -495,7 +487,7 @@ Not having a *known length* does not prevent any result from being created. Howe T __result = new T(); __result.Add(__e1); - __result[__k1] = __v1; + __result.Add(new T1(__k1, __v1)); foreach (var __t in __s1) __result.Add(__t); @@ -504,6 +496,23 @@ Not having a *known length* does not prevent any result from being created. Howe This allows creating the target type, albeit with no capacity optimization to prevent internal reallocation of storage. + * In this translation, `dictionary_element` is only supported if `T1` is known and is some `KeyValuePair<,>`. + + * If `T` is a dictionary collection initializer with key `K1` and value `V1`, the literal is translated as: + + ```c# + T __result = new T(capacity: __len); + + __result[__e1.Key] = __e1.Value; + __result[__k1] = __v1; + foreach (var __t in __s1) + __result[__t.Key] = __t.Value; + + // further additions of the remaining elements + ``` + + * In this translation, `expression_element` is only supported if the element type is some `KeyValuePair<,>` or `dynamic`, and `spread_element` is only supported if the enumerated element type is some `KeyValuePair<,>` or `dynamic`. + ### Unknown length translation [unknown-length-translation]: #unknown-length-translation @@ -515,11 +524,11 @@ Not having a *known length* does not prevent any result from being created. Howe T __result = new T(); __result.Add(__e1); - __result[__k1] = __v1; + __result.Add(new T1(__k1, __v1)); foreach (var __t in __s1) __result.Add(__t); - // further additions of the remaining elements + // further additions of the remaining elements ``` This allows spreading of any iterable type, albeit with the least amount of optimization possible. @@ -536,14 +545,14 @@ Not having a *known length* does not prevent any result from being created. Howe ```c# T1[] __result = .CreateArray( count_of_expression_elements + count_of_dictionary_elements); - __result = 0; + int __index = 0; .Add(ref __result, __index++, __e1); .Add(ref __result, __index++, new T1(__k1, __v1)); foreach (var __t in __s1) .Add(ref __result, __index++, __t); - // further additions of the remaining elements + // further additions of the remaining elements .Resize(ref __result, __index); ``` @@ -552,40 +561,8 @@ Not having a *known length* does not prevent any result from being created. Howe The counts passed to `CreateArray` are used to provide a starting size hint to prevent wasteful resizes. -## Dictionaries -[dictionaries]: #dictionaries - -[*Constructible collection types*](#constructible-collection-types) is updated to include dictionaries. - -The following are _constructible collection types_ that can be used to construct collection literals. - -* ... -* `Dictionary` -* Interface types `I` implemented by `Dictionary` (e.g. `IDictionary`, `IReadOnlyDictionary`) - -```c# -Dictionary x = []; -IReadOnlyDictionary y = ["one":1, "two":2]; -``` - -Dictionary construction uses `this[int index] { set; }` rather than `Add()` when to ensure consistent _overwrite semantics_ rather than _add semantics_. - -```c# -var x = [a:1, b:2]; // {{a, 1}, {b, 2}} -var y = [..x, b:4]; // {{a, 1}, {b, 4}} -var z = [a:3, ..x]; // {{a, 1}, {b, 2}} -``` - -As stated in [*natural type*](#natural-type): - -> If the [*natural element type*](#natural-element-type-updated) `T` can be determined and it is some `KeyValuePair`, then the *natural type* of the collection is `Dictionary`; otherwise, the *natural type* of the collection is `List`. - -If the *natural type* of the collection is `List` the literal is not allowed to contain a `dictionary_element`. - -The *natural element type* algorithm is updated as follows. - -### Natural element type (updated) -[natural-element-type-updated]: #natural-element-type-updated +## Natural element type +[natural-element-type]: #natural-element-type Computing the *natural element type* starts with three sets of types and expressions called *dictionary key set*, *dictionary value set*, and *remainder set*. @@ -963,7 +940,7 @@ Hopefully small questions: Span __s1 = b ? [c] : [d, e]; int __e2 = f; - Span __result = stackallock int[2 + __s1.Length]; + Span __result = stackalloc int[2 + __s1.Length]; int __index = 0; __result[__index++] = a;