Type-Level Generics — Series 3

Preview — 3 of 10 questions

javascript
type U2I<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
type LastOf<U> = U2I<U extends unknown ? () => U : never> extends () => infer R ? R : never;

type U2T<U, L = LastOf<U>> = [U] extends [never] ? [] : [...U2T<Exclude<U, L>>, L];

type R = U2T<"a" | "b" | "c">;
A["a" | "b" | "c"] — a union always collapses into a single element
BCompile-time error — Exclude inside a recursive alias is circular
C["c", "b", "a"] — elements are always produced in reverse order
D["a", "b", "c"], but the ordering is an implementation detail of the compiler rather than a guarantee

javascript
type Fn = (a: never) => unknown;

type Pipe<Fns extends readonly Fn[], In> =
  Fns extends readonly [(a: In) => infer R]
    ? R
    : Fns extends readonly [(a: In) => infer R, ...infer Rest extends readonly Fn[]]
      ? Pipe<Rest, R>
      : never;

type P = Pipe<[(a: number) => string, (b: string) => boolean], number>;
Astring — only the first function's return type is used
Bboolean — each step's output becomes the next step's input, and the last return type is the result
Cstring | boolean — the recursion unions every intermediate result
Dnever — the pattern cannot match a tuple of two functions

What are the types of a.mode and b.mode?

javascript
function define<T extends Record<string, string>>(config: T): T {
  return config;
}

function defineConst<const T extends Record<string, string>>(config: T): T {
  return config;
}

const a = define({ mode: "dark" });
const b = defineConst({ mode: "dark" });
ABoth are "dark" — a generic parameter always preserves literals
BBoth are string — object properties are always widened
Ca.mode is string; b.mode is "dark" — the const modifier on the type parameter applies as const-style inference at the call site
Da.mode is "dark"; b.mode is string — const widens

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.