Skip to content

fix: bind a const pattern element to its default rather than assigning it - #59

Merged
marevol merged 1 commit into
es6/for-of-destructuringfrom
es6/fix-const-pattern-default
Aug 28, 2026
Merged

fix: bind a const pattern element to its default rather than assigning it#59
marevol merged 1 commit into
es6/for-of-destructuringfrom
es6/fix-const-pattern-default

Conversation

@marevol

@marevol marevol commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Stacked on #58.

A const binding whose default was actually taken failed at run time:

const [a = 7] = [];       // TypeError: Assignment to constant "a"
const { x = 8 } = {};     // same
const [a = 7] = [1];      // 1, fine - the default was not needed

Cause

declareBindings emitted the declaration and then filled the default in with a follow-up assignment, if (a === undefined) a = 7;. A const binding cannot be assigned to a second time, so the guard threw whenever it fired. let and var were unaffected, which is why the default path looked like it worked.

Fix

The default now goes into the initializer instead, as a conditional. The value is read once into a temporary first and the temporary is what gets tested and used, rather than testing the read expression and then reading it again — a getter is still invoked exactly once, as it was with the guard.

Found while adding patterns to for-of (#58); it is not specific to loops and reproduces on a plain declaration.

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

…g it

A const binding whose default was actually taken failed at run time:

    const [a = 7] = [];         TypeError: Assignment to constant "a"
    const { x = 8 } = {};       same
    const [a = 7] = [1];        1, fine - the default was not needed

declareBindings emitted the declaration and then filled the default in with a
follow-up assignment, "if (a === undefined) a = 7;". A const binding cannot be
assigned to a second time, so the guard threw whenever it fired. let and var
were unaffected, which is why the default path looked like it worked.

The default now goes into the initializer instead, as a conditional. The value is
read once into a temporary first and the temporary is what gets tested and used,
rather than testing the read expression and then reading it again - a getter is
still invoked exactly once, as it was with the guard.

Found while adding patterns to for-of; it is not specific to loops and reproduces
on a plain declaration.

./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/fix-const-pattern-default branch from 68cb1de to ffc3485 Compare August 28, 2026 05:11
@marevol marevol self-assigned this Aug 28, 2026
@marevol
marevol merged commit b9ae972 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