ReviewOS

home-lang/home

fix(ts): contextually type values written into annotated Program types

#754
Closed glennmichael123 opened this 8 days ago ·
8 days ago

Parent

Evidence

#751 (8c587f6) projects the members an annotated receiver reads. The mirror position — a value written into such an annotation — still leaves nested callback parameters implicitly any. On the #688 module fixtures (util, errors, schemas), with a consumer that only writes:

import type * as schemas from "./schemas.js";

declare function takesSchema(schema: schemas.Schema): void;

const literal: schemas.Schema = {
  _zod: {
    run: (payload) => {                                   // TS7006 in Home
      payload.issues.push({ code: "invalid_type", expected: "string", input: payload.value });
      return payload;
    },
  },
};

takesSchema({
  _zod: {
    run: (payload) => payload,                            // TS7006 in Home
  },
});
EngineResult
TypeScript 6.0.3no diagnostics
Native TypeScript 7.0.2no diagnostics
Home 8c587f6fb (ReleaseSafe)consumer.ts:7:11 TS7006, consumer.ts:16:11 TS7006
Home 65f80a525 (before #688)identical to 8c587f6fb

Both engines contextually type payload from SchemaInternals.run. Schema is a projection_only Program export, so the annotation's own type stays any in Home and supplies no contextual type to the object literal's nested members. #751 deliberately did not change this: it resolves members that are read, never the annotation's whole type, because publishing the projection as the declared type added 38 Zod identities (recorded in TS_PERFORMANCE.md).

Fair oracle

Reuse the issue_688_* fixtures in packages/ts_program/src/ts_program.zig. Cover an annotated const initializer, a call argument, a nested property, and a returned literal. Each callback body must read a projected member (payload.issues) so silence is not mistaken for any, and each graph needs a deliberate invalid control.

Acceptance

  • TypeScript 6.0.3, tsgo 7.0.2, and Home agree by file, line, column, and code.
  • Contextual types only: the projection must not become the annotation's type, and writes must not be checked against an approximate whole type (a regression here shows up as new Zod TS2345/TS2322).
  • Exact Zod 4.5.2 core diagnostic identity A/B shows zero additions.
  • Full Program/checker suites and optimized builds pass, evidence documented and linked from the README.
8 days ago

Fixed in 168bb2a6a.

What was wrong

#751 made members read through a projection-only imported type resolve. Values written into one still did not, so every callback inside the literal fell back to an implicit any:

const literal: schemas.Schema = { _zod: { run: (payload) => payload } };
//                                             ^ TS7006, both engines report nothing

The fix

The literal takes its contextual target from the type that was written, not from its checked type — walking a binding's annotation, the callee's parameter annotation, or the enclosing literal's member until a reference to an imported declaration is reached. An overload set, or a name that walk cannot resolve uniquely, yields nothing rather than a guess.

Only the member the literal actually writes is lowered, and only as context. The literal's own type and every assignability check still see any — the same scope the read side uses. Publishing the whole projection as the annotation's type was measured and rejected under #751 at +38 Zod identities, because the approximation then becomes an assignment target. Context cannot reject a program that plain any accepted; it only replaces an implicit any parameter with the declared one.

Three-engine agreement

Each callback assigns payload to number, so the single TS2322 proves the declared type arrived rather than an implicit any being silenced.

PositionTypeScript 6.0.3Native 7.0.2Home 168bb2a6aHome 8c587f6fb
Binding annotationTS2322 5:13identicalidenticalTS7006 4:11
Call argumentTS2322 6:13identicalidenticalTS7006 5:11
Method shorthandTS2322 5:13identicalidenticalTS7006 4:9

Zod 4.5.2 core

8c587f6fb168bb2a6aChange
All diagnostics1461406 removed; 0 added
Unique identities1411356 removed; 0 added

Removed: core/api.ts:1850:56, core/memoizer.ts:97:9, 97:16, 97:25, 107:9, 121:10 — all TS7006. Both are the targeted shape: const memo: $ZodMemoizer = { alloc(_inst, payload, empty) { ... } } and const def: schemas.$ZodCustomStringFormatDef = { fn: (val) => ... }.

Worth stating plainly: both pinned engines report exactly one diagnostic on this graph (the locales module the shard omits) and no TS7006 anywhere. So every diagnostic Home still emits here is a false positive, and the six removals are unambiguous rather than a judgement call.

Scope of the measurement

The Zod figures cover the 21 src/v4/core/**/*.ts files only. The pinned tree's src/v4/locales shard was deleted by the host's periodic /private/tmp cleanup during #751, so that shard's identities are unmeasured. The corpus is now kept outside /tmp.

Program 213/213, checker 4371/4371, ReleaseSafe and ReleaseFast builds, zig fmt --check and git diff --check clean. The added test fails against 8c587f6fb with TS7006 at each of the three positions, so it discriminates. No timing is claimed (#548).

8 days ago

Closing as completed. The implementation and full acceptance evidence landed in 168bb2a and are ancestors of current origin/main: three-engine contextual-typing agreement, permanent positive/negative controls, zero added Zod identities, and passing Program/checker/build gates are recorded above.

Sign in to comment on this issue.