ADR-009 — TypeScript annotations as inference seeds, not ground truth

Status: Accepted Date: 2026-04-27

This ADR is a sub-decision of ADR-001 — it concerns how the compiler treats TypeScript type information.

Context

TypeScript's type system is unsound: it permits programs whose runtime behavior violates declared types. as casts, any, intersection types, function-parameter bivariance, and external .d.ts files that lie about their JS counterparts are all common sources of TS-passing programs whose runtime values do not match their declared shapes. TypeScript types are also erased — there is no runtime enforcement. Relying on TS annotations as ground truth for lowering decisions would produce incorrect code for any program that exploits this unsoundness, including programs that do so unintentionally.

At the same time, ignoring annotations entirely wastes information that narrows the inference search space substantially. A function declared (x: number) => number is a strong hypothesis — almost always the inference will eventually conclude the same thing, but it is much cheaper to start from that hypothesis than to derive it from scratch. Annotations also document programmer intent, which is a useful seed when the program is not yet self-consistent (for example, partway through a refactor).

Decision

TypeScript annotations are treated as initial constraints — hypotheses — for the whole-program analysis described in ADR-001. They seed the inference and reduce convergence cost, but every lowering decision requires proof from the analysis, not just a matching annotation. A number annotation on a parameter is a constraint; if the call graph cannot prove that all callers pass f64, the parameter stays externref and the function's body is compiled accordingly.

The TypeScript checker (used as the frontend, see ADR-011) also infers types for unannotated values from initializers and assignment patterns. These inferred types are seeded into the analysis on the same footing as explicit annotations.

Consequences

Positive: correct output for unsound TS programs — the compiler does not generate code that assumes a property that the runtime can violate. Higher inference precision than a fully annotation-free approach, because the analysis starts from useful seeds. Plain .js input (no annotations) is handled by the same machinery, just with fewer seeds.

Negative: requires a two-pass design — seed from annotations, then validate via analysis. Programs that do match their declared types incur the full analysis cost anyway; we cannot shortcut by trusting the annotation. Programmers may be surprised that an annotated number parameter compiles to an externref-typed Wasm parameter when the call graph cannot prove all callers conform.

Alternatives rejected