Parent: #904 Related: #928 (found while adversarially reviewing that fix), #929 (the opposite direction: valid code rejected)
Invalid programs accepted
Every row below was executed against node and zig-js, not read off the code. node rejects each as a SyntaxError; zig-js parses each successfully (OK, or a later runtime error such as ReferenceError — either way the parser accepted it).
All 20 are pre-existing. A test262 binary built from the parser before #928 gives the same result on every one; #928 neither introduced nor fixed any of them.
The 15 test262 subtrees gated for #928 (11,890 tests, 0 fail) do not exercise these shapes, so a green corpus says nothing about them.
1. Function bodies the scope walk never reaches — 13 cases
The same program inside a function body is checked if that function is a declaration, and silently accepted if it is almost anything else:
| program | node | zig-js |
|---|---|---|
(function(){ let x; var x; })(); | SyntaxError | accepts |
g0(function(){ let x; var x; }); | SyntaxError | accepts |
({ m(){ let x; var x; } }); | SyntaxError | accepts |
xx = () => { let y; var y; }; | SyntaxError | accepts |
function h(){ return function(){ let x; var x; } } | SyntaxError | accepts |
var a = 1, b = function(){ let x; var x; }; | SyntaxError | accepts |
let a2 = 1, b2 = class { m(){ let x; var x; } }; | SyntaxError | accepts |
for (var i = function(){ let x; var x; };;) break; | SyntaxError | accepts |
[function(){ let z; var z; }]; | SyntaxError | accepts |
class K { x = function(){ let z; var z; }; } | SyntaxError | accepts |
let {a = function(){ let z; var z; }} = {}; | SyntaxError | accepts |
new function(){ let x; var x; }; | SyntaxError | accepts |
function f2(a = function(){ let x; var x; }){} | SyntaxError | accepts |
Cause: recurseScope descends an expr_stmt only when the expression is a function or class, and a var_decl initializer only for single declarators. It never follows call arguments, object/array literals, assignments, return values, class field initializers, default parameter values, or the initializers inside a declaration group, destructuring declaration or for head. #928 preserved that reach exactly rather than widening it inside a performance change.
2. with bodies are never walked — 2 cases
| program | node | zig-js |
|---|---|---|
{ let x; with ({}) { var x; } } | SyntaxError | accepts |
with ({}) { let y; var y; } | SyntaxError | accepts |
Cause: neither the scope walk nor collectVarNames has a .with_stmt arm.
3. Labelled function declarations are not counted — 3 cases
| program | node | zig-js |
|---|---|---|
let g; lbl: function g(){} | SyntaxError | accepts |
function g2(){ let f; lbl: function f(){} } | SyntaxError | accepts |
{ let f; lbl: function f(){} } | SyntaxError | accepts |
Cause: only a direct func_decl statement is checked as a var-scoped name, and the lexical seen set has no labeled_stmt arm, so a labelled function is invisible to both sides.
4. Strict duplicate block functions checked with sloppy strictness — 2 cases
| program | node | zig-js |
|---|---|---|
function s(){ "use strict"; { function g(){} function g(){} } } | SyntaxError | accepts |
class C2 { m(){ { function g(){} function g(){} } } } | SyntaxError | accepts |
Cause (from reading, consistent with the executed result): addDecl decides rigidity from self.strict, but this check runs after the function body has been parsed, by which point parseFnBody's defer has already restored the enclosing sloppy strictness. Two block-level functions in strict code are then treated as Annex B.3.3 plain duplicates. Top-level strict code and class static blocks are checked during parsing and are unaffected.
Not verified by execution
Readings from the same review that I could not run, because test262 --eval has no module mode. Listed so they are not lost, and not claimed as confirmed:
- module top-level lexical names are never compared with nested vars —
let x; { var x; }as a module; export_declis not walked —export function f(){ let x; var x; }.
Suggested order
Each family is a separate root cause and can land separately. For each: pin the cases above as a parser test confirmed against node first, then change the walk, then re-run the #928 gate subtrees and the #928 frozen workloads — family 1 widens the walk's reach, so it is exactly the kind of change that could reintroduce the quadratic #928 removed.