A generic declaration takes types as parameters. Type parameters are erased: there is
no T at run time, so new T() and x instanceof T are
not possible
(Handbook:
Generics).
Type parameters
TypeParameters: < TypeParameter {, TypeParameter} [,] >
TypeParameter: {const | in | out} Name [extends Type] [= Type]
TypeArguments: < Type {, Type} >
Type parameters may be declared on functions, methods, arrow functions, function and constructor types, classes, interfaces and type aliases.
function identity<T>(x: T): T { return x; }
const arrow = <T>(x: T): T => x; // in .tsx write <T,>
class Stack<T> {
items: T[] = [];
push(x: T) { this.items.push(x); }
static create<U>() { return new Stack<U>(); }
}
interface Box<T> { value: T }
type Pair<A, B> = [A, B];
type Id = <T>(x: T) => T;
type Maker = new <T>(x: T) => T[];
A class’s type parameters are visible to its instance side only. A static member cannot use them, because there is one set of statics for all type arguments.
Constraints
T extends C requires each type argument to be assignable to
C. Inside the declaration, T can be used as if it were
C. A type parameter with no constraint is not assignable to
{}, because it could be null or undefined.
function longest<T extends { length: number }>(a: T, b: T) {
return a.length >= b.length ? a : b;
}
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const n = get({ a: 1 }, "a"); // number
get({ a: 1 }, "b"); // error
function sort<T extends { compare(other: T): number }>(x: T) {} // T may refer to itself
function copy<T extends U, U>(a: T, b: U) {} // or to another parameter
Defaults
A default (2.3) gives a type parameter a value when no argument is written and none is inferred. Parameters with defaults must come after those without. A default must satisfy the constraint, and may refer to earlier parameters.
interface Box<T = string> { value: T }
const b: Box = { value: "x" };
type Named<T, U = T[]> = [T, U];
type N = Named<number>; // [number, number[]]
type Wrong<T = string, U> = [T, U]; // error: required after optional
Writing a generic type with no arguments at all is an error unless all of its type parameters have defaults.
Variance annotations
Variance says how a generic type relates when its argument changes. If
Dog extends Animal, a covariant type has
Box<Dog> assignable to Box<Animal>, a
contravariant type the reverse, and an invariant type neither. The
compiler normally measures this from the structure. Since 4.7 it can be declared.
| Annotation | Meaning | Type parameter used |
|---|---|---|
out T | Covariant | Only in output positions |
in T | Contravariant | Only in input positions |
in out T | Invariant | In both |
type Getter<out T> = () => T;
type Setter<in T> = (value: T) => void;
interface State<in out T> {
get: () => T;
set: (value: T) => void;
}
interface Wrong<out T> { // error: T is used in an input position
set: (value: T) => void;
}
Annotations are allowed on classes, interfaces, and type aliases for object, function, constructor and mapped types. They are not allowed on functions or on aliases of other types, such as unions. Annotations document intent, catch mistakes, and can make checking of deep recursive types faster.
const type parameters
Adding const to a type parameter (5.0) makes inference treat literals at
the call site as if they had been written with
as const. It is allowed
on the type parameters of functions, methods and classes, and not on interfaces or type
aliases.
function plain<T>(x: T) { return x; }
function exact<const T>(x: T) { return x; }
const a = plain(["a", "b"]); // string[]
const b = exact(["a", "b"]); // readonly ["a", "b"]
const c = exact({ n: 1 }); // { readonly n: 1 }
function tuple<const T extends string[]>(x: T) { return x; }
const d = tuple(["a", "b"]); // ["a", "b"], not readonly
const list = ["a"];
const e = exact(list); // string[]: only literals at the call count
const keeps the exact type of the argument expressionInference
Type arguments are usually left out. The compiler collects candidates from each argument, then picks the type parameter’s type. Type arguments are all or nothing: if any is written, all must be written, apart from those with defaults (Handbook: Type Inference).
- With several candidates, the compiler chooses one that the others fit. If none
does, the call is an error, and the type argument must be written:
pair<string | number>(1, "a"). - With no candidates, the type parameter gets its default, then its constraint, then
unknown. - A literal candidate is kept if the type parameter has a primitive constraint, or
if it appears at the top level of the return type. Otherwise it widens, so
wrap("a")returningT[]givesstring[], whileT extends stringgives"a"[]. - Contextually typed callback parameters take part: arguments that need no context are used first, then the callbacks. Since 3.4 a generic function passed as an argument can remain generic in the result.
function pair<T>(a: T, b: T) {}
pair(1, "a"); // error: no common type
pair<string | number>(1, "a"); // fine
function two<A, B>(a: A, b: B) {}
two<string>("x", 1); // error: all or none
function compose<A, B, C>(f: (a: A) => B, g: (b: B) => C) {
return (a: A) => g(f(a));
}
const c = compose((x: number) => x + 1, y => y.toFixed());
// (a: number) => string
function nothing<T>(): T { return null as any; }
const n = nothing(); // unknown
NoInfer
NoInfer<T> (5.4) keeps a position from being used as a source of
inference.
function light<C extends string>(colours: C[], initial?: NoInfer<C>) {}
light(["red", "green"], "red"); // fine
light(["red", "green"], "blue"); // error: without NoInfer, "blue" would widen C
NoInfer leaves one argument out of the inferenceInstantiation expressions
Type arguments can be applied to a function or constructor without calling it (4.7). The result keeps the signatures that accept those arguments.
function box<T>(value: T) { return { value }; }
const stringBox = box<string>; // (value: string) => { value: string }
stringBox(1); // error
const ErrorMap = Map<string, Error>;
const m = new ErrorMap(); // Map<string, Error>
The parsing rules are on the Lexical structure page.
Variadic tuples
A tuple type can spread a generic tuple (4.0). This keeps the length and element types through a function.
type Concat<A extends unknown[], B extends unknown[]> = [...A, ...B];
type J = Concat<[1], [2, 3]>; // [1, 2, 3]
function tail<T extends unknown[]>(x: [unknown, ...T]): T {
const [, ...rest] = x;
return rest;
}
function args<T extends unknown[]>(...a: T): T { return a; }
const v = args("a", 1); // [string, number]
Recursive aliases
A type alias may refer to itself inside an object type, an array or a tuple (3.7), but not directly in a union member at the top of the alias.
type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
type Bad = string | Bad; // error: circular