ReviewOS

home-lang/home

fix(ts): resolve generic function declarations referenced before their declaration

#765
Open glennmichael123 opened this 8 days ago ·
8 days ago

Parent: #758 · Related: #548

A generic function declaration referenced before it is checked resolves to any, so its arguments get no contextual type. Zod core hits this at core/api.ts:1696, where _superRefine (declared at 1692) calls _check<T>((payload) => ...) and function _check<O> is declared at 1718: payload reports a false TS7006. The explicit <T> is irrelevant — a later-declared generic called with no type arguments behaves the same, and moving the declaration above its use clears it. A non-generic forward reference already works.

Cause: checkSourceFile never pre-computes signatures for top-level statements (precheckFunctionSignatures runs only inside function bodies), and the on-demand fallback ensurePlainFunctionSignature returns null for any function with type parameters, so resolveValueDeclInStmt falls through to any. The same fallback feeds functionDeclValueTypeForNamespaceMerge and the namespace-member lookup. TypeScript hoists declarations and resolves signatures lazily, so declaration order does not matter.

A first attempt simply computed the generic signature on demand (popping the type-parameter scope the signature pass pushes). It was measured and rejected: adversarial review showed the on-demand path runs while the caller's narrow scopes are still on the stack, so the callee's signature can bind the caller's type parameters, and the polluted signature is then cached on the HIR and reused (a false TS2345 for type T = {tag:string}; function f<T>() { g({tag:"x"}) } function g(x: T) {}, clean in tsgo). It also crashed the pinned Zod run and broke 15 checker tests. Only part of an overload set is registered by a forward reference as well, which can turn today's silent any into a false TS2554/TS2345/TS2769.

The direction worth measuring is pre-computing top-level function signatures in checkSourceFile — the way function bodies already do it — so the signature is resolved in a clean scope rather than at the use site. Gate any candidate on the unchanged Zod core shard plus both unit suites.

7 days ago

Reconfirmed on current origin/main 2eb23ca with installed TypeScript 5.7.3 and freshly built Home home-tsc, both under serial 3,840 MB guards. In a four-line forward generic identity fixture, TypeScript reports one TS2322 at (3,7), number to string; Home exits successfully with no diagnostic. The related forward callback-context fixture passes in both, consistent with 3b3dae8 fixing only contextual arguments. Current checkSourceFile still does not precompute top-level signatures and ensurePlainFunctionSignature still returns null for unchecked generic declarations. Prior global/on-demand candidates documented above regressed checker tests or crashed Zod through caller-scope capture, so I have not adopted an unsafe partial value-signature cache. #765 remains open for declaration-owned clean-scope resolution and overload preservation; no fix is claimed in this checkpoint.

8 days ago

Landed the bounded contextual portion in 3b3dae8ed5 and pushed it to origin/main.

The checker now resolves a callback parameter annotation contextually when the callee is a later, unchecked generic function declaration. The query uses a temporary clean type-parameter scope, binds explicit arguments plus simple inference/defaults/constraints, supports imported callback annotations through Program type lowering, discards speculative diagnostics, and never publishes or caches an early callee value type. This fixes declaration-order contextual typing while preserving caller binder isolation.

Rejected after measurement:

  • Global top-level signature precomputation fixed this case but regressed 65/4,379 checker tests by freezing class/interface/type-alias dependencies as any.
  • Suffix precomputation reduced that to five failures but still froze later unrelated signatures.
  • Reference-scoped precomputation passed both unit suites but corrupted Program cross-file payload state and crashed the pinned Zod run.
  • Generic on-demand value resolution also passed the focused case but produced the same Zod crash and risks incomplete overload registration.

Acceptance evidence:

  • Full checker: 4,379/4,379
  • Full Program: 216/216
  • ReleaseSafe home-tsc build: passed
  • Exact Zod v4.5.2 core baseline: 46 → 45 diagnostics, removing only api.ts:1696:25 TS7006, with zero additions
  • Independent TypeScript 6.0.2 oracle: both compilers retain the same two deliberate TS2322 errors and report no TS7006
  • Focused Pickier, zig fmt --check, and git diff --check: passed

I am leaving this issue open because complete forward value resolution and overload-set construction still need a safe clean-scope design. This commit completes the contextual callback acceptance slice without claiming that broader work.

8 days ago

Current-main 2026-10-23 reproduction on f859d64: a top-level call to a later generic identity<t>(value: T): T returns a number, then assigns the result to both number and string variables. Installed TypeScript 5.7.3 reports exactly one TS2322 (number to string) on the bad assignment. Freshly rebuilt Home home-tsc with ignoreConfig, strict, and noEmit reports no diagnostics. I added an uncommitted direct checker regression with the same source at packages/ts_checker/src/check.zig; its guarded focused run is red (expected one TS2322, found zero; 1/2 tests passed, peak tree footprint 1,824 MB). No fix, commit, or parity claim yet. Existing contextual-only forward callback handling does not provide a value/result signature for this top-level call; any fix must preserve clean-scope ownership and overload semantics described in this issue.

8 days ago

Added two current-main differential controls after the simple red case. With fresh f859d64 home-tsc and installed TypeScript 5.7.3 under serial 3,840 MB guards: a forward generic overload returns its inferred number in both compilers, yielding the same single TS2322 on string assignment. A lexical-owner control has a top-level type alias T, a caller generic parameter also named T, and a later non-generic function whose parameter uses the top-level alias. TypeScript reports only TS2322 on the deliberate bad assignment; Home reports that TS2322 plus a false TS2345 at the forward call. This narrows the design requirement: preserve the working overload set, and resolve a single forward declaration against its own lexical type bindings rather than the caller's active generic scope. No candidate code or passing fix is claimed.

Sign in to comment on this issue.