Parent: #459
Problem
Every SyntaxError zig-js raises from the parser or lexer is rendered mechanically as
<origin>: <ZigErrorEnumTag> at <line>:<col>. JavaScriptCore renders prose that names
the offending token. A differential over 67 cases matches 4/67.
| source | zig-js | JavaScriptCore |
|---|---|---|
eval("var q = ;") | eval: UnexpectedToken at 1:11 | Unexpected token ';' |
eval("if (true) {") | eval: ExpectedToken at 1:12 | Unexpected end of script |
eval("let d = 1; let d = 2;") | eval: UnexpectedToken at 1:26 | Cannot declare a let variable twice: 'd'. |
eval("return 1;") | eval: UnexpectedToken at 1:1 | Return statements are only valid inside functions. |
eval("1 = 2") | eval: InvalidAssignmentTarget at 1:3 | Left side of assignment is not a reference. |
eval("++1") | eval: InvalidAssignmentTarget at 1:4 | Prefix ++ operator applied to value that is not a reference. |
eval("var n = 0x") | eval: InvalidNumber at 1:13 | No hexadecimal digits after '0x' |
eval("var g = { get p(x) {} }") | eval: UnexpectedToken at 1:35 | Unexpected identifier 'x'. getter functions must have no parameters. |
eval("var p = { __proto__: 1, __proto__: 2 }") | eval: UnexpectedToken at 1:41 | Attempted to redefine __proto__ property. |
eval("class C { constructor(){} constructor(){} }") | eval: UnexpectedToken at 1:46 | Cannot declare multiple constructors in a single class. |
Three separable defects
1. The payload is an internal Zig error tag, not a message. ParseError has six
tags — UnexpectedToken, ExpectedToken, InvalidAssignmentTarget, InvalidNumber,
UnterminatedString, UnterminatedComment, UnexpectedCharacter — standing in for at
least 38 distinct JavaScriptCore diagnostics. UnexpectedToken alone covers 21 of them.
So this is not a string-tweak: the parser has to carry a reason discriminant (or at
minimum the offending token's text) to the throw site.
The mechanism already exists and is simply unused. eval("super()") produces
'super' is only valid inside a method — prose, no tag, no position — so a prose path
is already wired; the other 30+ sites do not use it.
2. zig-js appends at line:col; JavaScriptCore never puts a position in .message.
It carries position on the Error object instead. Even with perfect prose, the suffix
would still break any consumer matching on message text.
3. Where a position IS reported, it is frequently wrong. It is where the parser
stopped, not where the error is: eval("let d10 = 1; let d10 = 2;") reports 1:26
(end of source) rather than column 18 where the duplicate sits. The same end-of-source
position shows up for strict-mode octal, duplicate parameters, duplicate __proto__,
duplicate constructors, undeclared labels, and getter/setter arity.
Suggested shape
Add a reason discriminant carried alongside ParseError (the way #889/#892 added a
retained source span to AST nodes), set at each throw site, and render it at the boundary
that currently formats <tag> at <l>:<c>. Dropping the position suffix is independent of
the prose work and could land first.
Verification
scratchpad/sweep/probe.sh diffs both engines; the case set is sweep/. Drive probes
from inside a function — zig-js --eval runs script code, so an indirect eval shares
global scope with the harness and a case declaring var i silently truncates the run.