ReviewOS

zig-utils/zig-js

Oracle blind spot: home-tool (JSC) rejects every reference to a private method or accessor

#943
Closed glennmichael123 opened this 8 days ago ·
8 days ago

Scopes #933 item 9. home-tool is the JavaScriptCore oracle for error-message parity (CLAUDE.md §11), so anything it cannot run is a shape the parity work silently cannot check.

Scope, by execution

Each program in its own file, run with home-tool run (home-lang/home@0d09d38, esnext target) and node v24.4.1. Identical results for .ts and .js input.

formnodehome-tool
private field, instance / staticrunsruns
#x in o against a fieldrunsruns
private method or accessor declared but never referencedrunsruns (wrong shape, below)
this.#m(), instance private methodrunsSyntaxError
this.#x, private getterrunsSyntaxError
this.#x = v, private setterrunsSyntaxError
getter + setter pairrunsSyntaxError
static private method / getter / setterrunsSyntaxError
#x in o against an accessorrunsSyntaxError
reference from an arrow inside the classrunsSyntaxError
this.#x += 10, this.#x++runsSyntaxError
o?.#xrunsSyntaxError
[this.#x] = [13]runsSyntaxError

Every failure is the same message: SyntaxError: Cannot reference undeclared private names: "#x". So the blind spot is not only accessors, as the queue item said: it is every reference to a private method or accessor, instance or static, in every syntactic position. Private fields are unaffected.

Cause

home-tool emits a private method or accessor as a public, string-keyed member named "#m", so the private name is never declared and every reference to it is an early error in JSC:

class C { #m() { return 2; } get #g() { return 3; } }
console.log(Object.getOwnPropertyNames(C.prototype).join(","));
// node:      constructor
// home-tool: constructor,#m,#g

That second line is a separate, silent consequence: code that declares private methods but never references them runs under home-tool with public #m/#g properties on the prototype.

The emitter is packages/ts_emit/src/js_emit.zig. At the default esnext target supportsNativePrivateFields() is true, so the §4.A.7 downlevel path (hoisted function variables plus a _C_instances WeakSet) is off and the member is emitted in the class body — with its name written as a string key. Fields take a different emit path and keep their #.

Consequences for zig-js work

  • Error-message parity cannot use home-tool for any program that references a private method or accessor; node decides validity there, and message text for those shapes has no oracle until the emitter is fixed.
  • #929/#930-style checks that involve private methods or accessors need node for validity and a different JSC route for messages.

Done

The fix belongs in home-lang/home's emitter: emit private method and accessor names verbatim at native targets. Once fixed, the table above should read runs in every row, and the getOwnPropertyNames check should print only constructor.

Sign in to comment on this issue.