ReviewOS

zig-utils/zig-js

Parser: class static blocks reject valid var/function declarations

#929
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Parent: #904 Related: #928 (found while adversarially reviewing that fix)

Valid programs rejected as SyntaxError

Confirmed by executing both, not by reading code: node accepts each, zig-js rejects each with ERR UnexpectedToken.

programnodezig-js
class C { static { var f; function f(){} } }validSyntaxError
class C { static { function f(){} function f(){} } }validSyntaxError

This is the more serious direction of the two early-error bug families found alongside #928: code that works everywhere else fails to load in zig-js.

Pre-existing, not introduced by #928 — confirmed by running the same two programs through a test262 binary built from the parser before that fix, which rejects them identically.

Cause

The class static block is checked during class parsing with funcs_lexical = true:

try self.checkLexicalDupes(block.block, true, &lexical_scope);

and under forced strict mode, so a static block's function declarations are treated as lexical and rigid. But a ClassStaticBlockStatementList treats its top-level function declarations as var-scoped, the same as a function body (TopLevelLexicallyDeclaredNames / TopLevelVarDeclaredNames). Treating function f(){} as lexical then:

  1. makes it collide with var f — the first case;
  2. makes two plain function f(){} a rigid duplicate — the second case.

Passing funcs_lexical = false looks like the direct fix, since that is what function bodies already use. The existing comment on checkLexicalDupes claims the check "is free of false positives"; these two cases contradict that, so the comment should change with the fix.

Suggested order

  1. Pin both cases plus neighbours that must stay errors — class C { static { let f; var f; } }, class C { static { let f; function f(){} } } — in a parser test, confirmed against node first.
  2. Change the static-block call, and re-run the class subtrees plus annexB/language.
9 days ago

Fixed in 6da5c2d

The static block is now checked with funcs_lexical = false, the classification function bodies already use, so its top-level function declarations are var-scoped as ClassStaticBlockStatementList requires. Blocks nested inside the static block are unchanged: their functions stay lexical and strict-rigid.

The bug was much wider than this issue's two cases

I built a 220-program corpus of static block declarations from five independent angles: the full hoistable-declaration matrix, nesting, parity with strict function bodies, static-block-specific rules, and a completeness critic over the first four. I re-checked every verdict against node myself, then parsed each program with test262 binaries built before and after the change. Each program was wrapped in a never-called function so the result reflects parsing alone; the wrapper changed node's verdict on 0 of 220.

outcomecount
fixed — rejected before, valid per node50
regressed0
correct before and after167
wrong before and after, not caused by this change3

The 50 fixes cover every combination of plain, async, generator and async-generator functions with each other and with var; vars hoisting out of nested if, for, try, switch and do bodies; destructured and labelled vars; several static blocks in one class; class expressions; and static blocks in nested classes. All of these were rejected before.

test262 could not have caught any of it. Its static-block early-error tests (static-init-invalid-lex-dup.js, static-init-invalid-lex-var.js) assert only the error direction, and none are skipped or excluded. That is why the pre-fix parser passed 100% while rejecting valid code, and why the in-file test is the only gate on the valid direction.

Verification

  • parser class static blocks scope top-level functions as var declarations pins 8 of the fixed cases and 9 that must stay errors, including duplicate functions in a block nested inside the static block, so a future change cannot widen this past the top level unnoticed. 74/74 parser tests pass.
  • test262 before vs after, across statements/class, expressions/class, annexB/language, block-scope and function-code: 9,633 tests, 0 fail under both.

The three that stayed wrong

None are caused by this change; each fails identically before and after.

  • function g(){ "use strict"; { function f(){} function f(){} } } and its switch-case form: strict duplicate block functions checked after strictness is restored, already tracked as family 4 of #930.
  • class C { static { try {} catch ([e]) { var e; } } }: not specific to static blocks at all. A destructuring catch parameter wrongly allows a same-named var. Filed as #931 with the full executed rule.

Sign in to comment on this issue.