ReviewOS

home-lang/home

fix(ts): accept object spreads from nullable generic constraints

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

Problem

Home emits false TS2698 for a nullable generic object spread:

function spread<Meta extends object | undefined>(value: Meta | undefined) {
  return { ...value };
}

TypeScript 6.0.3 and the TypeScript 7 native preview accept the same strict, no-emit source.

This surfaced while giving WeakMap#get its correct V | undefined result in #662; the pinned Zod graph then exposed the same defect in registries.ts.

Root cause

objectSpreadSourceIsValid recognizes type parameters constrained to nullish-only types inside unions, but reads the constraint's aggregated flags without first excluding union/intersection constraints. object | undefined therefore carries is_undefined and is incorrectly skipped as if the entire constraint were undefined, leaving no spreadable branch.

Fair oracle

Positive:

  • T extends object | undefined
  • operand T | undefined
  • object spread of that operand
  • TypeScript 6.0.3 and TypeScript 7: 0 diagnostics
  • Home parent: false TS2698

Negative control:

  • A hard T & undefined intersection remains TS2698, matching both reference compilers and the existing permanent regression.

Acceptance criteria

  • Recurse through union/intersection constraints instead of treating their aggregated nullish flags as nullish-only.
  • Preserve the hard-nullish intersection rejection.
  • Add permanent regression coverage.
  • Verify no new diagnostic identities on the pinned Zod graph.

Blocks the zero-addition admission gate for #662; tracks #416 and #548.

8 days ago

The conditional/mapped follow-up is now live on main in 7e2fcbd (fix(ts): evaluate conditional spread branches (#663)). It resolves only branches proven by the check type constraint, recursively validates both reachable branches otherwise, and treats mapped types as object shapes.

Fair validation:

  • focused checker regression: exact two TS2698 negatives (primitive-only and mixed conditional branches); nullable object, object/object conditional, mapped, and full Zod-style nested aliases remain clean
  • TypeScript 6.0.3 and native TypeScript preview: the same exact two TS2698 negatives on the standalone oracle
  • optimized Home checker: the same exact two TS2698 negatives
  • combined pinned Zod 4.5.2 candidate with #662/#664: 436 diagnostics / 431 unique locations -> 430 / 425, six removals and zero additions

The combined corpus result is not attributed solely to this issue; this change removes the pre-existing registries.ts:66:25 TS2698 and prevents the collection-resolution change from exposing a new TS2698 at registries.ts:68:26. Full-suite and documentation evidence will follow before closure.

8 days ago

Completed on main in 127c714 and 7e2fcbd; the combined published audit is in f26b768 and README. TypeScript 6.0.3, the native TypeScript preview, and ReleaseFast Home accept the nullable, mapped, nested Zod-like, and object-only spread cases while all report exactly two TS2698 diagnostics for the primitive-only and mixed controls. The unchanged Zod 4.5.2 audit finishes at 430 diagnostics / 425 unique identities with zero candidate-only identities. Full checker and Program suites plus a clean-cache ReleaseFast build pass. Closing as completed.

Sign in to comment on this issue.