Skip to content

fix(parser): don't extend yield with member access, calls or binary operators - #669

Merged
3cp merged 1 commit into
meriyah:mainfrom
MaxFreedomPollard:fix/yield-without-operand
Oct 23, 2026
Merged

3cp merged 1 commit into
meriyah:mainfrom
MaxFreedomPollard:fix/yield-without-operand

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

A YieldExpression is a complete AssignmentExpression. It can't be the object of a member access, the callee of a call, a template tag, or the left operand of a binary operator. Meriyah parses yield in parsePrimaryExpression, and the callers then continue it as they would a primary expression.

Invalid code is accepted:

parse('function* g() { return yield.x; }') // ReturnStatement(MemberExpression)
parse('function* g() { yield > 1 }')       // BinaryExpression
parse('function* g() { yield?.x }')        // ChainExpression
parse('function* g() { [yield ?? x] }')    // LogicalExpression

function *g() { return yield.x; } is fail/84633c379e4010bf.js in tc39/test262-parser-tests, and esprima and babel have the same input as invalid-yield-generator-member-expression. V8 and acorn reject all four.

Valid code gets the wrong AST. yield [no LineTerminator here] AssignmentExpression is a restricted production, so after a line break an operand-less yield ends and ASI starts a new statement:

function* g() {
  yield
  (x) // should be `yield; (x);`, meriyah produced `(yield)(x)`
}

The same thing happens with [x] (a MemberExpression), a template (a TaggedTemplateExpression) and +x or -x (a BinaryExpression). It also happens when a yield argument ends in one of these cases, as in yield yield\n(x) or yield x => {}\n(x). Before this PR only yield\n*a was handled, by a special check.

Fix: after a YieldExpression, if the current token is a member, call or template token or a binary operator:

  • with no operand and on the same line, the parser reports Unexpected token;
  • otherwise it sets a new Flags.DisallowContinuation. parseMemberOrUpdateExpression and parseBinaryExpression check and clear this flag and return without consuming the token, the same way Flags.DisallowCall and Flags.DisallowInvoke work for block-body arrows. The statement then ends through ASI, or the enclosing parser reports the token ((yield\n(x)) is still an error).

These are unchanged: (yield).x, yield, x, yield\n++x, yield\n/re/, and (with webcompat) for (var a = yield b in c);.

Tests: 10 fail and 8 pass cases in test/parser/expressions/yield.ts, with snapshots.

Verification: I couldn't install the dev dependencies, so I couldn't run vitest. Instead I ran the test files on Node 22 (--experimental-transform-types) with a small stand-in for vitest's describe/it/expect. The stand-in serializes snapshots the way vitest does: on main it runs the 139 unit test files (91,991 tests, all passing) and reproduces all 101 .snap files byte for byte. With this change all 92,009 tests pass, and the only snapshot changes are the 18 new entries. I also compared results before and after the change for:

  • test262, at the pinned commit and at current HEAD, with the CI options and with each of webcompat, lexical and next off: identical.
  • AST alignment with acorn over 48,682 test262 fixtures, with preserveParens on and off: identical.
  • 13,828 files (babel parser and generator fixtures, esprima fixtures, tc39/test262-parser-tests, redux-saga, acorn), each parsed as script and as module, with and without JSX: only four files differ, all of which now fail to parse. Three are the yield.x files linked above. The fourth is babel's v8intrinsic fixture yield %StringParseInt(...), which V8 also rejects without --allow-natives-syntax.

🤖 Generated with Claude Code

… operators

A YieldExpression is a complete AssignmentExpression, but it was parsed as a
primary expression, so callers went on to parse member accesses, calls,
tagged templates and binary operators after it. Invalid code such as
`function* g() { yield.x }` or `yield > 1` was accepted, and valid code that
relies on ASI after an operand-less `yield` (`yield\n(x)`, `yield\n[x]`,
`yield\n+x`) was parsed into a single wrong expression.

Report an operand-less `yield` followed on the same line by such a token,
and otherwise set `Flags.DisallowContinuation` so the caller stops before
the token.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@pkg-pr-new

pkg-pr-new Bot commented Oct 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/meriyah@669

commit: cd70298

@3cp 3cp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thx!

@3cp
3cp merged commit 7bcc6ae into meriyah:main Oct 23, 2026
8 checks passed
@3cp

3cp commented Oct 25, 2026

Copy link
Copy Markdown
Member

v7.4.1 is released.

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