From 79be266c9dc23a19a1514841108a918c7ea901de Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 26 Jun 2025 16:47:40 -0400 Subject: [PATCH 1/6] Fix tuple assignment Fixes #1155 This PR follows the process outlined in https://github.com/dotnet/csharpstandard/issues/1155#issuecomment-2284833989 --- standard/conversions.md | 2 +- standard/expressions.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index bca951adf..a799f681e 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -345,7 +345,7 @@ In all cases, the rules ensure that a conversion is executed as a boxing convers ### 10.2.13 Implicit tuple conversions -An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`’s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order from left to right by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the implicit conversion found, and initializing the field with the result. +An implicit conversion exists from an expression `E` with a tuple type `S` to a tuple type `T` if `S` has the same arity as `T` and an implicit conversion exists from each element type in `S` to the corresponding element type in `T`. If an element name in the tuple expression does not match a corresponding element name in the tuple type, a warning shall be issued. diff --git a/standard/expressions.md b/standard/expressions.md index 15e98f08e..cb503e0d3 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -33,12 +33,12 @@ An ***instance accessor*** is a property access on an instance, an event access ### 12.2.2 Values of expressions -Most of the constructs that involve an expression ultimately require the expression to denote a ***value***. In such cases, if the actual expression denotes a namespace, a type, a method group, or nothing, a compile-time error occurs. However, if the expression denotes a property access, an indexer access, or a variable, the value of the property, indexer, or variable is implicitly substituted: +Most of the constructs that involve an expression ultimately require the expression to denote a ***value***. In such cases, if the actual expression denotes a namespace, a type, a method group, or nothing, a compile-time error occurs. However, if the expression denotes a property access, an indexer access, a tuple, or a variable, the value of the property, indexer, tuple, or variable is implicitly substituted: - The value of a variable is simply the value currently stored in the storage location identified by the variable. A variable shall be considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) before its value can be obtained, or otherwise a compile-time error occurs. - The value of a property access expression is obtained by invoking the get accessor of the property. If the property has no get accessor, a compile-time error occurs. Otherwise, a function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed, and the result of the invocation becomes the value of the property access expression. - The value of an indexer access expression is obtained by invoking the get accessor of the indexer. If the indexer has no get accessor, a compile-time error occurs. Otherwise, a function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed with the argument list associated with the indexer access expression, and the result of the invocation becomes the value of the indexer access expression. -- The value of a tuple expression is obtained by applying an implicit tuple conversion ([§10.2.13](conversions.md#10213-implicit-tuple-conversions)) to the type of the tuple expression. It is an error to obtain the value of a tuple expression that does not have a type. +- The value of a tuple expression is the value obtained by evaluating the tuple expression (§12.8.6). It is an error to obtain the value of a tuple expression that does not have a type. ## 12.3 Static and Dynamic Binding @@ -1611,7 +1611,7 @@ A tuple expression has a type if and only if each of its element expressions `Ei A tuple expression is evaluated by evaluating each of its element expressions in order from left to right. -A tuple value can be obtained from a tuple expression by converting it to a tuple type ([§10.2.13](conversions.md#10213-implicit-tuple-conversions)), by reclassifying it as a value ([§12.2.2](expressions.md#1222-values-of-expressions))) or by making it the target of a deconstructing assignment ([§12.21.2](expressions.md#12212-simple-assignment)). +A tuple value is obtained from a tuple expression by evaluating it and storing the result in corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order from left to right by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the implicit conversion found, and initializing the field with the result. > *Example*: > From 0b0a41d136950bbab6a049233f21c4df0354ad9d Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 26 Jun 2025 17:25:33 -0400 Subject: [PATCH 2/6] Type inference for tuple elements Fixes #1244 I followed the recommended text from [collection expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) because we well need them in the future. I did break apart the first phase and the input type inference because tuple elements now needs subscripts as well. We could put them together, but the small `i` and `j` start to look the same. --- standard/expressions.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index cb503e0d3..b05d3e94c 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -763,14 +763,7 @@ Type inference takes place in phases. Each phase will try to infer type argument #### 12.6.3.2 The first phase -For each of the method arguments `Eᵢ`: - -- If `Eᵢ` is an anonymous function, an *explicit parameter type inference* ([§12.6.3.8](expressions.md#12638-explicit-parameter-type-inferences)) is made *from* `Eᵢ` *to* `Tᵢ` -- Otherwise, if `Eᵢ` has a type `U` and the corresponding parameter is a value parameter ([§15.6.2.2](classes.md#15622-value-parameters)) then a *lower-bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `Tᵢ`. -- Otherwise, if `Eᵢ` has a type `U` and the corresponding parameter is a reference parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)), or output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)) then an *exact inference* ([§12.6.3.9](expressions.md#12639-exact-inferences)) is made *from* `U` *to* `Tᵢ`. -- Otherwise, if `Eᵢ` has a type `U` and the corresponding parameter is an input parameter ([§15.6.2.3.2](classes.md#156232-input-parameters)) and `Eᵢ` is an input argument, then an *exact inference* ([§12.6.3.9](expressions.md#12639-exact-inferences)) is made *from* `U` *to* `Tᵢ`. -- Otherwise, if `Eᵢ` has a type `U` and the corresponding parameter is an input parameter ([§15.6.2.3.2](classes.md#156232-input-parameters)) then a *lower bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `Tᵢ`. -- Otherwise, no inference is made for this argument. +For each of the method arguments `Eᵢ`, an input type inference is made from `Eᵢ` to the corresponding parameter type `Tᵢ`. #### 12.6.3.3 The second phase @@ -798,10 +791,23 @@ An *unfixed* type variable `Xᵢ` *depends directly on* an *unfixed* type varia `Xₑ` *depends on* `Xᵢ` if `Xₑ` *depends directly on* `Xᵢ` or if `Xᵢ` *depends directly on* `Xᵥ` and `Xᵥ` *depends on* `Xₑ`. Thus “*depends on*” is the transitive but not reflexive closure of “*depends directly on*”. +#### §input-type-inference Input type inferences + +An *input type inference* is made *from* an expression `E` *to* a type `T` in the following way: + +- If `E` is a tuple expression (§12.8.6) with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with a corresponding element type `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ`, an input type inference is made from `Eᵢ` to `Tₑ`. +- If `E` is an anonymous function, an *explicit parameter type inference* ([§12.6.3.8](expressions.md#12638-explicit-parameter-type-inferences)) is made *from* `E` *to* `T` +- Otherwise, if `E` has a type `U` and the corresponding parameter is a value parameter ([§15.6.2.2](classes.md#15622-value-parameters)) then a *lower-bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `T`. +- Otherwise, if `E` has a type `U` and the corresponding parameter is a reference parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)), or output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)) then an *exact inference* ([§12.6.3.9](expressions.md#12639-exact-inferences)) is made *from* `U` *to* `T`. +- Otherwise, if `E` has a type `U` and the corresponding parameter is an input parameter ([§15.6.2.3.2](classes.md#156232-input-parameters)) and `E` is an input argument, then an *exact inference* ([§12.6.3.9](expressions.md#12639-exact-inferences)) is made *from* `U` *to* `T`. +- Otherwise, if `E` has a type `U` and the corresponding parameter is an input parameter ([§15.6.2.3.2](classes.md#156232-input-parameters)) then a *lower bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `T`. +- Otherwise, no inference is made for this argument. + #### 12.6.3.7 Output type inferences -An *output type inference* is made *from* an expression `E` *to* a type T in the following way: +An *output type inference* is made *from* an expression `E` *to* a type `T` in the following way: +- If `E` is a tuple expression with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` a corresponding element type `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ` an output type inference is made from `Eᵢ` to `Tₑ`. - If `E` is an anonymous function with inferred return type `U` ([§12.6.3.13](expressions.md#126313-inferred-return-type)) and `T` is a delegate type or expression tree type with return type `Tₓ`, then a *lower-bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `Tₓ`. - Otherwise, if `E` is a method group and `T` is a delegate type or expression tree type with parameter types `T₁...Tᵥ` and return type `Tₓ`, and overload resolution of `E` with the types `T₁...Tᵥ` yields a single method with return type `U`, then a *lower-bound inference* is made *from* `U` *to* `Tₓ`. - Otherwise, if `E` is an expression with type `U`, then a *lower-bound inference* is made *from* `U` *to* `T`. From 93287f0e238a219b735c1421c6b5c24af009a302 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 2 Jul 2025 16:21:52 -0400 Subject: [PATCH 3/6] Apply suggestions from code review Changes made during the July meeting. Co-authored-by: Jon Skeet --- standard/expressions.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index b05d3e94c..2a4cc1332 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -763,7 +763,7 @@ Type inference takes place in phases. Each phase will try to infer type argument #### 12.6.3.2 The first phase -For each of the method arguments `Eᵢ`, an input type inference is made from `Eᵢ` to the corresponding parameter type `Tᵢ`. +For each of the method arguments `Eᵢ`, an input type inference (§input-type-inference) is made from `Eᵢ` to the corresponding parameter type `Tᵢ`. #### 12.6.3.3 The second phase @@ -795,7 +795,8 @@ An *unfixed* type variable `Xᵢ` *depends directly on* an *unfixed* type varia An *input type inference* is made *from* an expression `E` *to* a type `T` in the following way: -- If `E` is a tuple expression (§12.8.6) with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with a corresponding element type `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ`, an input type inference is made from `Eᵢ` to `Tₑ`. +- If `E` is a tuple expression (§12.8.6) with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ`, an input type inference is made from `Eᵢ` to `Tₑ`. +- ``` - If `E` is an anonymous function, an *explicit parameter type inference* ([§12.6.3.8](expressions.md#12638-explicit-parameter-type-inferences)) is made *from* `E` *to* `T` - Otherwise, if `E` has a type `U` and the corresponding parameter is a value parameter ([§15.6.2.2](classes.md#15622-value-parameters)) then a *lower-bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `T`. - Otherwise, if `E` has a type `U` and the corresponding parameter is a reference parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)), or output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)) then an *exact inference* ([§12.6.3.9](expressions.md#12639-exact-inferences)) is made *from* `U` *to* `T`. @@ -807,7 +808,7 @@ An *input type inference* is made *from* an expression `E` *to* a type `T` in th An *output type inference* is made *from* an expression `E` *to* a type `T` in the following way: -- If `E` is a tuple expression with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` a corresponding element type `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ` an output type inference is made from `Eᵢ` to `Tₑ`. +- If `E` is a tuple expression with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ` an output type inference is made from `Eᵢ` to `Tₑ`. - If `E` is an anonymous function with inferred return type `U` ([§12.6.3.13](expressions.md#126313-inferred-return-type)) and `T` is a delegate type or expression tree type with return type `Tₓ`, then a *lower-bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `Tₓ`. - Otherwise, if `E` is a method group and `T` is a delegate type or expression tree type with parameter types `T₁...Tᵥ` and return type `Tₓ`, and overload resolution of `E` with the types `T₁...Tᵥ` yields a single method with return type `U`, then a *lower-bound inference* is made *from* `U` *to* `Tₓ`. - Otherwise, if `E` is an expression with type `U`, then a *lower-bound inference* is made *from* `U` *to* `T`. From b823bd0aed5a8051ceac66673fee84a40027f5a5 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 3 Jul 2025 14:47:08 -0400 Subject: [PATCH 4/6] Revert "Fix tuple assignment" This reverts commit 79be266c9dc23a19a1514841108a918c7ea901de. --- standard/conversions.md | 2 +- standard/expressions.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/standard/conversions.md b/standard/conversions.md index a799f681e..bca951adf 100644 --- a/standard/conversions.md +++ b/standard/conversions.md @@ -345,7 +345,7 @@ In all cases, the rules ensure that a conversion is executed as a boxing convers ### 10.2.13 Implicit tuple conversions -An implicit conversion exists from an expression `E` with a tuple type `S` to a tuple type `T` if `S` has the same arity as `T` and an implicit conversion exists from each element type in `S` to the corresponding element type in `T`. +An implicit conversion exists from a tuple expression `E` to a tuple type `T` if `E` has the same arity as `T` and an implicit conversion exists from each element in `E` to the corresponding element type in `T`. The conversion is performed by creating an instance of `T`’s corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order from left to right by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the implicit conversion found, and initializing the field with the result. If an element name in the tuple expression does not match a corresponding element name in the tuple type, a warning shall be issued. diff --git a/standard/expressions.md b/standard/expressions.md index 2a4cc1332..a5aaf89c5 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -33,12 +33,12 @@ An ***instance accessor*** is a property access on an instance, an event access ### 12.2.2 Values of expressions -Most of the constructs that involve an expression ultimately require the expression to denote a ***value***. In such cases, if the actual expression denotes a namespace, a type, a method group, or nothing, a compile-time error occurs. However, if the expression denotes a property access, an indexer access, a tuple, or a variable, the value of the property, indexer, tuple, or variable is implicitly substituted: +Most of the constructs that involve an expression ultimately require the expression to denote a ***value***. In such cases, if the actual expression denotes a namespace, a type, a method group, or nothing, a compile-time error occurs. However, if the expression denotes a property access, an indexer access, or a variable, the value of the property, indexer, or variable is implicitly substituted: - The value of a variable is simply the value currently stored in the storage location identified by the variable. A variable shall be considered definitely assigned ([§9.4](variables.md#94-definite-assignment)) before its value can be obtained, or otherwise a compile-time error occurs. - The value of a property access expression is obtained by invoking the get accessor of the property. If the property has no get accessor, a compile-time error occurs. Otherwise, a function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed, and the result of the invocation becomes the value of the property access expression. - The value of an indexer access expression is obtained by invoking the get accessor of the indexer. If the indexer has no get accessor, a compile-time error occurs. Otherwise, a function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed with the argument list associated with the indexer access expression, and the result of the invocation becomes the value of the indexer access expression. -- The value of a tuple expression is the value obtained by evaluating the tuple expression (§12.8.6). It is an error to obtain the value of a tuple expression that does not have a type. +- The value of a tuple expression is obtained by applying an implicit tuple conversion ([§10.2.13](conversions.md#10213-implicit-tuple-conversions)) to the type of the tuple expression. It is an error to obtain the value of a tuple expression that does not have a type. ## 12.3 Static and Dynamic Binding @@ -1618,7 +1618,7 @@ A tuple expression has a type if and only if each of its element expressions `Ei A tuple expression is evaluated by evaluating each of its element expressions in order from left to right. -A tuple value is obtained from a tuple expression by evaluating it and storing the result in corresponding `System.ValueTuple<...>` type, and initializing each of its fields in order from left to right by evaluating the corresponding tuple element expression of `E`, converting it to the corresponding element type of `T` using the implicit conversion found, and initializing the field with the result. +A tuple value can be obtained from a tuple expression by converting it to a tuple type ([§10.2.13](conversions.md#10213-implicit-tuple-conversions)), by reclassifying it as a value ([§12.2.2](expressions.md#1222-values-of-expressions))) or by making it the target of a deconstructing assignment ([§12.21.2](expressions.md#12212-simple-assignment)). > *Example*: > From 6625556a4d73558f53aa0b6c19778d8f7a87236e Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 3 Jul 2025 14:50:55 -0400 Subject: [PATCH 5/6] fix lint issue --- standard/expressions.md | 1 - 1 file changed, 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index a5aaf89c5..3600b1d85 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -796,7 +796,6 @@ An *unfixed* type variable `Xᵢ` *depends directly on* an *unfixed* type varia An *input type inference* is made *from* an expression `E` *to* a type `T` in the following way: - If `E` is a tuple expression (§12.8.6) with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ`, an input type inference is made from `Eᵢ` to `Tₑ`. -- ``` - If `E` is an anonymous function, an *explicit parameter type inference* ([§12.6.3.8](expressions.md#12638-explicit-parameter-type-inferences)) is made *from* `E` *to* `T` - Otherwise, if `E` has a type `U` and the corresponding parameter is a value parameter ([§15.6.2.2](classes.md#15622-value-parameters)) then a *lower-bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `T`. - Otherwise, if `E` has a type `U` and the corresponding parameter is a reference parameter ([§15.6.2.3.3](classes.md#156233-reference-parameters)), or output parameter ([§15.6.2.3.4](classes.md#156234-output-parameters)) then an *exact inference* ([§12.6.3.9](expressions.md#12639-exact-inferences)) is made *from* `U` *to* `T`. From 4555d09190b8e84b769b1fe30b23956d6b0aad19 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 7 Jul 2025 15:16:40 -0400 Subject: [PATCH 6/6] Update standard/expressions.md Co-authored-by: Jon Skeet --- standard/expressions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index 3600b1d85..67e4b9221 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -807,7 +807,7 @@ An *input type inference* is made *from* an expression `E` *to* a type `T` in th An *output type inference* is made *from* an expression `E` *to* a type `T` in the following way: -- If `E` is a tuple expression with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ` an output type inference is made from `Eᵢ` to `Tₑ`. +- If `E` is a tuple expression with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ` an output type inference is made from `Eᵢ` to `Tₑ`. - If `E` is an anonymous function with inferred return type `U` ([§12.6.3.13](expressions.md#126313-inferred-return-type)) and `T` is a delegate type or expression tree type with return type `Tₓ`, then a *lower-bound inference* ([§12.6.3.10](expressions.md#126310-lower-bound-inferences)) is made *from* `U` *to* `Tₓ`. - Otherwise, if `E` is a method group and `T` is a delegate type or expression tree type with parameter types `T₁...Tᵥ` and return type `Tₓ`, and overload resolution of `E` with the types `T₁...Tᵥ` yields a single method with return type `U`, then a *lower-bound inference* is made *from* `U` *to* `Tₓ`. - Otherwise, if `E` is an expression with type `U`, then a *lower-bound inference* is made *from* `U` *to* `T`.