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.
| form | node | home-tool |
|---|---|---|
| private field, instance / static | runs | runs |
#x in o against a field | runs | runs |
| private method or accessor declared but never referenced | runs | runs (wrong shape, below) |
this.#m(), instance private method | runs | SyntaxError |
this.#x, private getter | runs | SyntaxError |
this.#x = v, private setter | runs | SyntaxError |
| getter + setter pair | runs | SyntaxError |
| static private method / getter / setter | runs | SyntaxError |
#x in o against an accessor | runs | SyntaxError |
| reference from an arrow inside the class | runs | SyntaxError |
this.#x += 10, this.#x++ | runs | SyntaxError |
o?.#x | runs | SyntaxError |
[this.#x] = [13] | runs | SyntaxError |
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,#gThat 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.