typescript / expert
Snippet
Compile-Time Type Equality Assertions for Type Testing
Library authors test runtime behavior with Vitest or Jest, but type-level contracts need their own gate. The `Equal<A, B>` trick exploits the fact that two function types with internal conditionals are assignable to each other only if those conditionals are identical for every `T` — which forces structural and variance equality, unlike the naive `A extends B ? B extends A ? true : false : false` that bivariantly accepts subtypes. `Expect<T extends true>` then weaponises this: the type only resolves when `T` is `true`, so a failing assertion lights up the file in red. Pair this with `tsc --noEmit` in CI and your `.d.ts` shipped to users is locked.
snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Equal<A, B> =(<T>() => T extends A ? 1 : 2) extends(<T>() => T extends B ? 1 : 2)? true: false;type Expect<T extends true> = T;// Test cases — these are compile-time assertions, not runtime checks:type _01 = Expect<Equal<Awaited<Promise<Promise<string>>>, string>>;type _02 = Expect<Equal<Capitalize<'foo'>, 'Foo'>>;type _03 = Expect<Equal<NonNullable<string | null | undefined>, string>>;// Naive equality FAILS to distinguish these — `Equal` succeeds:type Naive<A, B> = A extends B ? (B extends A ? true : false) : false;type N1 = Naive<{ a: 1 }, { a: 1; b?: 2 }>; // true (wrong!)type E1 = Equal<{ a: 1 }, { a: 1; b?: 2 }>; // false (correct)// Would fail to compile — intentional:// type _bad = Expect<Equal<string, 'foo'>>;
Breakdown
1
(<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2)
Wraps the equality check inside generic function types; assignability holds only when the inner conditional behaves identically for every `T`.
2
type Expect<T extends true> = T;
Constraint `T extends true` makes the alias unsatisfiable when the equality returns `false` — that becomes the compile error.
3
type _01 = Expect<Equal<Awaited<Promise<Promise<string>>>, string>>;
Asserts that `Awaited` recursively unwraps nested Promises — the kind of contract you'd otherwise verify by hand-checking docs.
4
type N1 = Naive<{ a: 1 }, { a: 1; b?: 2 }>;
Shows why bidirectional `extends` is not enough: an optional property is bivariantly assignable in both directions and the naive check passes incorrectly.