ReviewOS

home-lang/home

fix(ts): instantiate dependent keyof constraints before validation

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

Follow-up under #548 and #416 after #657.

The pinned Zod 4.5.2 graph has 43 TS2344 diagnostics. Forty-one in src/v4/core/api.ts share one exact shape: a literal type argument such as "coerce" or "when" is checked against Exclude<keyof T["_zod"]["def"], ...> after T has been supplied as a concrete schema interface, but Home validates the literal against the unsubstituted declaration constraint.

A strict same-file reduction defines ExtraKey<T extends Base, K extends Exclude<keyof T["_zod"]["def"], "type" | "checks" | "error"> = never> and instantiates it with a concrete StringSchema. TypeScript 6.0.3 and native TypeScript 7.0.2 accept "coerce" and "when", then report exactly one TS2344 for appended "missing" with the resolved constraint "coerce" | "when". Current ReleaseFast Home reports three TS2344 diagnostics, including the two valid keys, and renders the unresolved generic constraint.

Resolve each later parameter constraint in the effective earlier-argument substitution environment before validating its explicit argument. Preserve dependent defaults, constraint provenance and diagnostic wording; keep the invalid literal rejected. Do not special-case key text, Zod names, Exclude, or suppress TS2344.

Add permanent positive/appended-negative controls calibrated against TS 6.0.3 and native TS 7.0.2, run the complete checker and Program suites, and compare every pinned Zod diagnostic identity against the current 489/484 checkpoint and immutable 602 baseline. Keep performance timing gated on zero under #548.

Provenance: #657 implementation 5f31fc7; docs 2143a46.

8 days ago

Implementation candidate is ready for release.

Root cause and fix:

  • generic type-alias argument validation had two paths that checked every formal against its declaration-time constraint independently;
  • the declaration-time Exclude<keyof T["_zod"]["def"], ...> had already collapsed to a non-free placeholder, so TypeId substitution could not recover the supplied earlier T;
  • both validation paths now accumulate earlier effective arguments left-to-right;
  • for a local declaration-backed parameter, validation re-lowers the original constraint syntax in a narrow scope binding those earlier formals, then checks the current argument against that exact result;
  • finite string-literal union constraints render from their resolved TypeIds, independent of unrelated alias display names.

No key text, Zod identifier, or Exclude special case is used.

Fair oracle:

  • TS 6.0.3: valid "coerce" / "when" accepted; appended "missing" reports exactly 1x TS2344 against "coerce" | "when";
  • native TS 7.0.2: same;
  • ReleaseFast Home candidate: same exact code count and resolved constraint wording.

Validation:

  • focused dependent-constraint test: pass;
  • broad constraint-filtered checker suite: pass;
  • checker: 4,354/4,354 pass;
  • Program: 189/189 pass;
  • scoped Pickier and git diff --check: pass.

Pinned Zod 4.5.2 audit:

  • #657 checkpoint: 489 diagnostics / 484 unique keys;
  • candidate: 448 / 443;
  • delta: 41 TS2344 identities removed, 0 added;
  • TS2344: 43 -> 2;
  • versus immutable 602-diagnostic / 597-key baseline: 154 unique keys removed, 0 added.

Timing remains gated under #548 because the graph is not yet clean, and unrelated Zig builds shared the host during ReleaseFast compilation.

8 days ago

Pushed directly to origin/main as 0ffa17565 (fix(ts): instantiate dependent alias constraints (#658)). The pushed tree is 0/0 aligned; author/committer is Chris Breuer and there is no coauthor trailer. Closing with the calibrated evidence above.

8 days ago

Documentation is live on origin/main as f880159a5: detailed dependent generic-alias constraint audit plus the linked compact README table. Final checkpoint: 448 diagnostics / 443 unique keys, 41 TS2344 removed, 0 added.

Sign in to comment on this issue.