ReviewOS

home-lang/home

TS emit: immediate arrow invocation loses call grouping

#755
Closed chris opened this 9 days ago ·
9 days ago

Problem

The JS emitter drops the grouping/call around an immediately invoked arrow expression. A value-producing arrow IIFE is emitted as an arrow whose body is called instead:

class P { x = 18; arrow() { return (() => this.x)(); } }
class F { #x = 18; arrow() { return (() => this.#x)(); } }
class A {
  #v = 18;
  get #x() { return this.#v; }
  arrow() { return (() => this.#x)(); }
}
console.log(JSON.stringify([new P().arrow(), new F().arrow(), new A().arrow()]));

Executed on current main (64d7b37b6) with Node v24.18.0 and a freshly built home-tool:

  • Node: [18,18,18]
  • home-tool: [null,null,null] (JSON.stringify renders the returned functions as null)

home-tsc shows the structural defect directly. For the accessor case it emits:

arrow() {
  return () => this.#x();
}

The required form is return (() => this.#x)();. This is a general arrow/call precedence problem: public properties, private fields, and private accessors all reproduce it. It was found while validating zig-utils/zig-js#943 and is independent of that private-declaration fix.

Acceptance

  • Parenthesized arrow expressions retain the grouping required when used as call/new/member operands.
  • Immediate invocation returns the arrow body result for public, private-field, and private-accessor members.
  • Nested/async/generic/JSX-shaped arrows and ordinary uncalled arrows preserve current output.
  • Add focused emitter and end-to-end runtime cases; compare the emitted program and result with Node.

No-workaround boundary

Do not special-case private names or these fixture strings. Fix precedence/parenthesization at the arrow-expression call boundary.

9 days ago

Implemented on main in 332fa5e81 (fix(ts-emit): preserve arrow operand grouping).

Validation:

  • focused ts_emit suite passes
  • rebuilt home-tsc emits grouped call, optional-call, new, member, unary, conditional, nested, async, generic, JSX, type-erased, and tagged-template arrow operands
  • emitted runtime matrix produces [18,18,18,4,5,6,0,"function",9] under Node at both ES2022 and ES5
  • freshly rebuilt home-tool produces the same result, including public, private-field, and private-accessor cases
  • tagged-arrow runtime emits ok

Sign in to comment on this issue.