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