Skip to content

discards, deconstruction, and assignment - #1730

Open
BillWagner wants to merge 3 commits into
dotnet:draft-v8from
BillWagner:assignment-and-discards
Open

discards, deconstruction, and assignment#1730
BillWagner wants to merge 3 commits into
dotnet:draft-v8from
BillWagner:assignment-and-discards

Conversation

@BillWagner

Copy link
Copy Markdown
Member

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_elements, which can be either variable_references or discard_tokens.

Fixes dotnet#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.
@BillWagner
BillWagner requested a review from Nigel-Ecma July 13, 2026 19:10
@BillWagner BillWagner added the meeting: priority Review before meeting. Merge, merge with issues, or reject at the next TC49-TC2 meeting label Jul 13, 2026

@jskeet jskeet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me, thanks Bill!

@Nigel-Ecma Nigel-Ecma left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for spotting my egg-on-face typos!

I think there are some changes required, partly due to confusion over the use of “matching”.

Comment thread standard/expressions.md Outdated
Comment thread standard/statements.md Outdated
#### 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In C# (but not all languages) deconstruction is distinct from pattern matching. The former selects values from a compound value and cannot fail, the latter matches against a value and can fail.

So this change as written is wrong, there is no matching involved. This either needs rewriting or simply reverting:

Suggested change
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.
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*.

The “curious” semantic here is that “zero or more” iteration variables are allowed. Given the semantics are partly reconstructed based on an implementation it should be determined whether this is an “accident of implementation” and the number should really be “one or more”.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I accepted the suggestion, and then unresolved to make sure I remember to address the curious semantic here.

Comment thread standard/statements.md Outdated
Comment thread standard/statements.md Outdated
```

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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change doesn’t read well to me – it is saying each *declaration_expression" declares a variable…

I suggest either rewriting or reverting as for the above cases, but do not forget the “curious” semantic mentioned for 1444 above and maybe “revert” to “one or more”.

Comment thread standard/statements.md
Comment thread standard/statements.md Outdated
```

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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you are reverting/rewriting following the preceding suggestions do the same here.

Comment thread standard/statements.md
@jskeet

jskeet commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

(Process note: I'll review again after @BillWagner has responded to @Nigel-Ecma's comments.)

@jskeet

jskeet commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Taking offline to try to get the changes requested resolved between meetings.

@jskeet jskeet removed the meeting: priority Review before meeting. Merge, merge with issues, or reject at the next TC49-TC2 meeting label Jul 22, 2026
Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com>
@BillWagner

Copy link
Copy Markdown
Member Author

Small example to demonstrate the "curious semantic":

using System;
using System.Collections.Generic;
using System.Linq;

// Allowed: A deconstructing foreach can discard all iteration variables.
foreach ((_, _) in Pairs())
{
    Console.WriteLine("==");
}

// Error CS8186: A foreach must declare its iteration variables.
foreach (_ in Enumerable.Range(1,10))
{
    Console.WriteLine("=");
}

static IEnumerable<(int, int)> Pairs() => Enumerable.Range(1,10).Select(n => (n, n + n));

@BillWagner

Copy link
Copy Markdown
Member Author

Small example to demonstrate the "curious semantic":

...

The decision is recorded in LDM notes here. The key quote:

We can also consider allowing top-level discards in foreach loops:

foreach (_ in e) { ... } // I don't care about the values

But that does not seem too important, especially since you can use var _ to the same effect.

I'll add a note to that effect.

- Reworded: the deconstructor now contains the iteration-variable declarations — each  *declaration_expression*  is one such declaration, and each discard declares none."
- The top-level "curious semantic" thread: Confirmed by design — LDM-2016-11-15 explicitly considered and deprioritized top-level  foreach (_ in e) ; discards were designed as deconstruction elements, and Roslyn accepts all-discard deconstructing foreach. Keeping 'zero or more'." Added the note on the curious semantics.
@BillWagner

Copy link
Copy Markdown
Member Author

@jskeet @Nigel-Ecma I apologize for missing the last set of comments before the meeting. I updated the text today. I also checked the LDM notes for the justification for why a deconstructing foreach can have 0 iteration variables, but a synchronous foreach isn't allowed to. I added a note to that effect.

@BillWagner

Copy link
Copy Markdown
Member Author

The base branch draft-v8 has been updated following the latest TC49-TG2 committee meeting. Please rebase this PR onto the latest draft-v8 and resolve any conflicts. Thanks!

@jskeet

jskeet commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@BillWagner: This is your PR; was the comment requesting a rebase an automated one?

@BillWagner

BillWagner commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

This is your PR; was the comment requesting a rebase an automated one?

Yes. As part of the work that led to #1754 I required that the agents not rebase any PR that was assigned to a committee member. I didn't want it to shift history underneath someone actively working. Instead, it left a comment indicating that a rebase might be needed because the base branch changed. I discovered a number of new requirements this month because we merged both draft-v8 PRs and draft-v9 feature PRs. It was useful, and I'll respond to your comments on that PR later. (and schedule a walk through with you.)

For this one, I've made the changes we discussed at the meeting, and it's ready for a final approval from you and @Nigel-Ecma

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Assignment and discards

3 participants