ReviewOS

home-lang/home

fix(ts): align Function predicate narrowing with declared global shape

#467
Closed chris opened this 8 days ago ·
8 days ago

Discovered while auditing repeated builtin Function construction for #457 (performance tracker #416). With strict/noLib/noEmit and the usual minimal global declarations plus interface Function { call(thisArg: any, ...args: any[]): any; }, both TS 6.0.3 and native TS 7.0.2 accept the following, while Home at 6a78f4e rejects the call and assignment with TS2349/TS2322:

function isCallable(value: unknown): value is Function {
  return typeof value === "function";
}
declare const value: number | (() => number);
if (isCallable(value)) {
  const result: number = value();
  const exact: () => number = value;
} else {
  const result: number = value;
}

A predicate targeting an alias of Function reproduces the call error too. An important control: replacing the global Function declaration with an empty interface makes TS 6 and TS 7 retain the union and correctly reject the positive-branch call. Preserve that declared-shape distinction; do not merely treat every name spelled Function as callable.

Relevant implementation: predicateTargetIsFunctionObject constructs a fresh builtin Function object then compares TypeIds, but internObjectType deliberately bypasses deduplication. Whole-object memoization would therefore change predicate behavior. The performance work will preserve fresh outer identities and reuse internal immutable construction components; this issue tracks the semantic correction independently.

Acceptance: cover direct and aliased predicates, positive/negative branches, empty versus callable global Function shapes, and negative typed-consumption controls against both pinned TS versions; run checker/conformance gates and avoid spelling-only or benchmark-specific shortcuts.

8 days ago

Additional baseline probe: adding declare const standaloneFunction: () => number; const builtinFunctionAssignment: Function = standaloneFunction; introduces no extra diagnostic in Home, and TS 6/TS 7 still accept the entire project. The existing errors remain confined to the direct/aliased predicate branches. This distinguishes the narrowing problem from ordinary function-to-Function assignment. The performance change under #457 preserves fresh outer Function identities and does not claim to resolve this issue.

8 days ago

The narrowly scoped performance change b5b5bd9 has been pushed to main. It preserves fresh outer Function identities and only reuses the fixed internal members. The direct/aliased predicate reproduction produces exactly the same three diagnostics as the preserved baseline, while TS 6.0.3 and native TS 7.0.2 still accept it; ordinary standalone Function assignment remains accepted. All 4,219 checker tests, 173 driver tests, and shared benchmark controls pass. This issue is intentionally still open: no semantic fix is claimed, and the failing probe is excluded from accepted timing workloads.

8 days ago

Completed on main in 730af1d. Predicate narrowing now resolves the synthetic Function target to the declared global shape by semantic type identity, including aliases: callable Function declarations narrow both branches correctly, while empty Function declarations retain the union. Added direct/alias, positive/negative, invalid-consumer, empty-shape, and standalone-assignment regressions. Verification: focused checker 2/2; full checker 4,409/4,409; default conformance 1,419/1,419; fresh stripped ReleaseSafe home-tsc matched TypeScript 6.0.3 and native TypeScript 7.0.2 exactly (callable fixture clean; empty fixture TS2349 + TS2322); formatter, diff check, and targeted Pickier passed. Guarded build peak: 2,592 MB. Pushed directly to origin/main under glennmichael123, with no PR or coauthor.

Sign in to comment on this issue.