From a715959c04d4bbcb4747a0e6ad15f4c59b840c7a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 13 Jul 2026 15:06:42 -0400 Subject: [PATCH] discards, deconstruction, and assignment Fixes #1630 Modify the language for deconstructing assignment (and the related `foreach` expansion to decouple the number of variables assigned from the classification as a deconstruction. It should be the presence of *deconstructor_element*s, which can be either *variable_reference*s or *discard_token*s. --- standard/expressions.md | 16 +++++++++++++++- standard/statements.md | 25 +++++++++++++++++++------ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 593fcfb96..72de032f4 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -6722,7 +6722,7 @@ If the input can be syntactically recognised as both a *deconstructing_assignmen > *Note*: ANTLR grammar semantics enforce this requirement due to the ordering of the alternatives. *Semantically* there is no overlap between the four alternatives, this is a syntactic disambiguation. -The *simple_assignment* and *compound_assignment* expressions assign a new value to a variable, a property, or an indexer element. Event assignment ([§12.23.6](expressions.md#12236-event-assignment)), a subset of *compound_assignment*, assigns a new value to an event. The *ref_assignment* expression assigns a variable reference ([§9.5](variables.md#95-variable-references)) to a reference variable ([§9.7](variables.md#97-reference-variables-and-returns)). The *deconstructing_assignment* assigns values to two or more targets. +The *simple_assignment* and *compound_assignment* expressions assign a new value to a variable, a property, or an indexer element. Event assignment ([§12.23.6](expressions.md#12236-event-assignment)), a subset of *compound_assignment*, assigns a new value to an event. The *ref_assignment* expression assigns a variable reference ([§9.5](variables.md#95-variable-references)) to a reference variable ([§9.7](variables.md#97-reference-variables-and-returns)). The *deconstructing_assignment* assigns values to the non-discard targets of a *deconstructor*, which has two or more *deconstructor_element*s. The target of a *simple_assignment*, *ref_assignment*, or any target of a *deconstructing_assignment* may be a discard ([§9.2.9.2](variables.md#9292-discards)). The left operand of a *compound_assignment* shall not be a discard. When the left operand of an assignment is a discard, the corresponding right-side expression is evaluated but no value is stored. @@ -6916,6 +6916,7 @@ It is a compile time error if any *variable_reference*, including any reclassifi --> There are restrictions on which *deconstructor_element*s are valid in a given context which are not expressed in the grammar: +- a simple discard (a *discard_token* that is *not* reclassified as a *variable_reference*, see above) is treated as neither a *declaration_expression* nor a *variable_reference*; it may occur as a *deconstructor_element* in any context and does not affect the restrictions applied to the other elements; - a *declaration_expression* can only occur if the containing *deconstructor* is at the start of a *statement* or a member of a *for_initializer*; and - a *variable_reference* can only occur if the containing *deconstructor*: - **is not** at the start of a statement, or @@ -6951,6 +6952,19 @@ The run-time processing of a deconstructing assignment, now `d = e`, proceeds as > *Note*: The construction of intermediate tuples produced by this algorithm might be elided by an implementation as specified by [§8.3.11.2](types.md#83112-eliding-intermediate-tuple-creation). *end note* + + + +> *Example*: A discard is a placeholder rather than a *variable_reference*, so it can be combined with an existing variable on the left side of a deconstructing assignment: +> +> +> ```csharp +> int x = 0; +> (x, _) = (1, 2); // assigns 1 to the existing variable x; the discard ignores 2 +> Console.WriteLine(x); +> ``` +> +> *end example* #### 12.23.3.2 Abridged deconstructors diff --git a/standard/statements.md b/standard/statements.md index 4b808aa77..c1ab3b919 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -1441,9 +1441,9 @@ The local variable `d` is not visible to or accessible to any user code. In par #### 13.9.5.4 Deconstructing foreach -A deconstructing foreach replaces the declaration and initialisation of a single iteration variable per iteration, in the synchronous and asynchronous foreach statements, with a collection of zero or more iteration variables declared per iteration within the *deconstructor*([§12.23.3](expressions.md#12233-deconstructing-assignment)) of a *deconstructing_assignment*. +A deconstructing foreach replaces the declaration and initialisation of a single iteration variable per iteration, in the synchronous and asynchronous foreach statements, with a *deconstructor* ([§12.23.3](expressions.md#12233-deconstructing-assignment)) whose *deconstructor_element*s are matched, per iteration, against the elements obtained by deconstructing each collection element. -All variables assigned to by the *deconstructor* must be declared within the *deconstructor*, it is a compile time error for any *deconstructor_element* to be a *variable_reference*. +Each *deconstructor_element* shall be either a *declaration_expression*, which declares an iteration variable, or a discard, which is a placeholder that matches its corresponding element without declaring a variable. It is a compile-time error for any *deconstructor_element* to be a *variable_reference*. A foreach statement of the form: @@ -1471,10 +1471,10 @@ is semantically equivalent to: } ``` -This follows the behavior of synchronous foreach ([§13.9.5.2](statements.md#13952-synchronous-foreach)), differing by replacing the delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns zero or more initialisation variables: +This follows the behavior of synchronous foreach ([§13.9.5.2](statements.md#13952-synchronous-foreach)), differing by replacing the declaration and initialisation of a single iteration variable with a *deconstructing_assignment* whose *deconstructor* declares an iteration variable for each *declaration_expression* it contains (discards declare none): - `C` and `E` are determined as for synchronous foreach -- `e` is not visible or accessible anywhere in the program accept as indicated in the above code +- `e` is not visible or accessible anywhere in the program except as indicated in the above code - the variables declared by the «deconstructor» are read-only to the «embedded_statement» - the code in the `finally` block is determined as for synchronous foreach @@ -1504,12 +1504,25 @@ is semantically equivalent to: } ``` -This follows the behavior of asynchronous foreach ([§13.9.5.3](statements.md#13953-asynchronous-foreach)), differing by replacing the delaration and initialisation of a single iteration variable with a *deconstructing_assignment* which declares and assigns zero or more initialisation variables: +This follows the behavior of asynchronous foreach ([§13.9.5.3](statements.md#13953-asynchronous-foreach)), differing by replacing the declaration and initialisation of a single iteration variable with a *deconstructing_assignment* whose *deconstructor* declares an iteration variable for each *declaration_expression* it contains (discards declare none): -- `enumerator` is not visible or accessible anywhere in the program accept as indicated in the above code +- `enumerator` is not visible or accessible anywhere in the program except as indicated in the above code - the variables declared by the «deconstructor» are read-only to the «embedded_statement» - the code in the `finally` block is determined as for asynchronous foreach +> *Example*: A deconstructing foreach uses discards as placeholders for elements that are not needed. Here each element of the collection is a tuple whose second element is discarded: +> +> +> ```csharp +> var points = new List<(int X, int Y)> { (1, 2), (3, 4) }; +> foreach (var (x, _) in points) +> { +> Console.WriteLine(x); +> } +> ``` +> +> *end example* + ## 13.10 Jump statements ### 13.10.1 General