Skip to content

feat: accept a destructuring pattern as the for-of loop variable - #58

Merged
marevol merged 1 commit into
es6/fix-code-cache-languagefrom
es6/for-of-destructuring
Aug 28, 2026
Merged

feat: accept a destructuring pattern as the for-of loop variable#58
marevol merged 1 commit into
es6/fix-code-cache-languagefrom
es6/for-of-destructuring

Conversation

@marevol

@marevol marevol commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Stacked on #57.

for-of (#39) and destructuring (#38) shipped separately and their combination did not parse, including the shape the two are most often used together in:

for (const [key, value] of pairs) { ... }   // Expected = but found of

Cause

isForOf() required an IDENT where the loop variable goes, so a pattern was never recognised as a for-of head at all and the of fell through to the last switch.

Fix

It now also accepts a bracket or a brace and scans to the bracket that closes it, the way isDestructuringAssignment does. That scan goes through lookahead() (#51), so it leaves the token stream alone and a regular expression in the source is still readable afterwards.

forOf() then parses the loop variable as a pattern and, per iteration, binds the element to a temporary and takes the pattern apart from there — so the element expression is evaluated once, as it is for a plain loop variable. The two existing emitters do the work: declareBindings for var, let and const, assignBindings for the form with no declaration, where the leaves are assignment targets rather than names to declare.

Covered: array and object patterns, renaming, rest, nesting, defaults, the pairs idiom, member expressions as targets, per-iteration let capture, and break/continue.

Not in this change

The same pattern in a for-in head. for-in yields keys, which are strings, so destructuring one is legal ES6 but of no practical use; for (var [a, b] in obj) stays a syntax error.

Verification

./gradlew build testOptimistic testPessimistic

suite before after
test 666, 0 fail 666, 0 fail
testOptimistic 1718, 0 fail 1718, 0 fail
testPessimistic 1718, 0 fail 1718, 0 fail

for-of and destructuring shipped separately and their combination did not parse,
including the shape the two are most often used together in:

    for (const [key, value] of pairs) { ... }   Expected = but found of

isForOf() required an IDENT where the loop variable goes, so a pattern was never
recognised as a for-of head at all and the "of" fell through to the last switch.
It now also accepts a bracket or a brace and scans to the bracket that closes it,
the way isDestructuringAssignment does. That scan goes through lookahead(), so it
leaves the token stream alone and a regular expression in the source is still
readable afterwards.

forOf() then parses the loop variable as a pattern and, per iteration, binds the
element to a temporary and takes the pattern apart from there - so the element
expression is evaluated once, as it is for a plain loop variable. The two
existing emitters do the work: declareBindings for var, let and const,
assignBindings for the form with no declaration, where the leaves are assignment
targets rather than names to declare.

Covered: array and object patterns, renaming, rest, nesting, defaults, the
pairs idiom, member expressions as targets, per-iteration let capture, and
break/continue.

Not in this change: the same pattern in a for-in head. for-in yields keys, which
are strings, so destructuring one is legal ES6 but of no practical use;
for (var [a, b] in obj) stays a syntax error.

Also unchanged, and pre-existing rather than introduced here: a const pattern
element whose default is actually taken fails with "Assignment to constant".
const [a = 7] = [] fails the same way outside a loop.

./gradlew build testOptimistic testPessimistic:

  suite            before         after
  test             666, 0 fail    666, 0 fail
  testOptimistic   1718, 0 fail   1718, 0 fail
  testPessimistic  1718, 0 fail   1718, 0 fail
@marevol
marevol force-pushed the es6/for-of-destructuring branch from b74ffe0 to 7cefe8c Compare August 28, 2026 05:11
@marevol marevol self-assigned this Aug 28, 2026
@marevol
marevol merged commit 1f3d4a1 into master Aug 28, 2026
1 check passed
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.

1 participant