I am interested in understanding how Typescript infers types in signatures using conditional types.
Example 1: demonstrates correct inference of T
as number
:
type Args<T> = { value: T }
function foo<T>(args: Args<T>) { return args; }
// correctly inferred as foo<number>
foo({ value: 123 });
Example 2: explores conditional types and the inference of unknown
:
type StringOrNever<T> = T extends string ? string : never;
type Args<T> = { value: StringOrNever<T>; }
function foo<T>(args: Args<T>) { return args; }
// both calls inferred as foo<unknown> :(
foo({ value: 123 });
foo({ value: "some string" });
Example 3: discusses expected inference but with unusual types:
type StringOrNever<T> = T extends string ? string : never;
type Args<T> = { value: StringOrNever<T> & T; }
function foo<T>(args: Args<T>) { return args; }
// inferred as foo<number>
foo({ value: 123 });
// inferred as foo<"Typescript">
foo({ value: "Typescript" });
My questions are:
- Why does Typescript infer
unknown
forT
in example 2, and what causes it to infer types as expected in example 3? - Is there a general algorithm that Typescript follows to infer generic type arguments, such as trying a sequence of candidates where the first candidate is always the argument type and the last resort is
unknown
?