Skip to content

Only start a destructuring pattern at the head of an AssignmentExpression - #1652

Open
andreasrosdal wants to merge 4 commits into
quickjs-ng:masterfrom
nordstjernen-web:fix-destructuring-only-at-head
Open

Only start a destructuring pattern at the head of an AssignmentExpression#1652
andreasrosdal wants to merge 4 commits into
quickjs-ng:masterfrom
nordstjernen-web:fix-destructuring-only-at-head

Conversation

@andreasrosdal

Copy link
Copy Markdown
Contributor

[a] = b and {a} = b are AssignmentExpressions whose target happens to be written like a literal. They are only reachable through AssignmentExpression : LeftHandSideExpression = AssignmentExpression, so an array or object literal anywhere else in an expression cannot begin one — 0 || [a] = b has no parse, because a LogicalORExpression is not a valid assignment target.

js_parse_postfix_expr() decides purely by peeking past the closing bracket for a =, without knowing where in the expression it is, so it accepts programs with no parse:

0 || [a] = b;      // -> 1
0 || {p:a} = b;    // -> [object Object]

V8 rejects both with Invalid left-hand side in assignment.

Add PF_PATTERN, set by js_parse_assign_expr2() when it starts the ConditionalExpression that may turn out to be an assignment target, and threaded down the unary/binary chain. Every recursive call that descends into a non-leftmost operand clears it, so only the leftmost literal is considered.

Testing

No change against master in any of:

suite result
language/expressions/assignment 5/483 (unchanged)
language/destructuring 1/19 (unchanged)
language/statements/for-of 0/751
language/expressions/arrow-function 0/343
language/statements/variable 0/178
language/expressions/conditional 0/20

🤖 Generated with Claude Code

https://claude.ai/code/session_014mv33YvfHz7t9mmkituBnn


Generated by Claude Code

claude and others added 4 commits August 6, 2026 17:56
…sion

`[a] = b` and `{a} = b` are AssignmentExpressions whose target happens to
be written like a literal. They are only reachable through
AssignmentExpression : LeftHandSideExpression = AssignmentExpression, so an
array or object literal anywhere else in an expression cannot begin one:
`0 || [a] = b` has no production, because a LogicalORExpression is not a
valid assignment target.

js_parse_postfix_expr() decides purely by peeking past the closing bracket
for a '=', without knowing where in the expression it is, so it accepts
programs with no parse:

    0 || [a] = b;      // -> 1
    0 || {p:a} = b;    // -> [object Object]

V8 rejects both with "Invalid left-hand side in assignment".

Add PF_PATTERN, set by js_parse_assign_expr2() when it starts the
ConditionalExpression that may turn out to be an assignment target, and
threaded down the unary/binary chain. Every recursive call that descends
into a non-leftmost operand clears it, so only the leftmost literal is
considered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mv33YvfHz7t9mmkituBnn
Covers the positions where an object/array literal must stay a literal
(binary, short circuit, unary and private-name-in operands) and the head
positions that must keep parsing as a pattern: statement level, both
branches of a conditional, comma elements, call arguments, literal
elements, initialisers, default parameter values, arrow bodies and the
three for loop forms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6eRbuuuCujKgQkrHgvMrc
`#field in {} = 0` must be an early SyntaxError. It was not one because the
`{}` was taken for a destructuring pattern on the strength of the `=` that
follows, which is exactly what this change stops; test262's
private-field-invalid-assignment-target now passes in both modes.

Leaving the two entries behind makes the full test262 run exit non-zero for
errors that are fixed rather than new. Also covers the construct in the
local test, next to the array form already there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6eRbuuuCujKgQkrHgvMrc
The rule is positional, so the test is mostly a list of positions. Added
the ones the first round did not name: a NewExpression callee and
argument, an update operand, a member access or call on a literal, an
optional chain, every compound assignment operator, the head of each
statement that takes a full Expression, an argument, array element,
property value and spread, the operand of yield and await, a class field
initialiser, a default parameter value, a template substitution and the
tag of a tagged template.
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.

2 participants