[TypeScript] infer
Res1: https://www.typescript-training.com/course/making-typescript-stick/08-type-challenges/#returnoff
Res2: https://learntypescript.dev/09/l2-conditional-infer
There is an infer
keyword that can be used within a condition in a conditional type to put the inferred type into a variable. That inferred variable can then be used within the conditional branches.
type ArrayElementType<T> = T extends (infer E)[] ? E : T;
// type of item1 is number
type item1 = ArrayElementType<number[]>
// type of item2 is `{name: string}`
type item2 = ArrayElementType<{name: string}>
For item1, T is number[], so (infer E)[] would match number[], therefore E is number type
For item2, T is {name: string}, so (infer E)[] doesn't match (not array of any type), therefore E would be T which is {name: string}
Infer function return type example:
type ReturnOf<F> = F extends (...args: any[]) => infer RT ? RT: F
// F extends (...args: any[]) => any: F should be a function type
// F extends (...args: any[]) => infer RT: the function return type is assigned to a variable RT
// infer RT ? RT: F: is RT a truth type? if yes return RT, otherwise return F
// the following test case can be pass
type cases = [
// simple 1
Expect<Equal<boolean, ReturnOf<() => boolean>>>,
// simple 2
Expect<Equal<123, ReturnOf<() => 123>>>,
Expect<
Equal<ComplexObject, ReturnOf<() => ComplexObject>>
>,
Expect<
Equal<
Promise<boolean>,
ReturnOf<() => Promise<boolean>>
>
>,
Expect<Equal<() => "foo", ReturnOf<() => () => "foo">>>,
Expect<
Equal<"heads" | "tails", ReturnOf<typeof flipCoin>>
>,
Expect<
Equal<
"rock" | "paper" | "scissors",
ReturnOf<typeof rockPaperScissors>
>
>
]
Enforce the `F` to be a function type:
type ReturnOf<F> = F extends (...args: any[]) => infer RT ? RT: F
const person = { name: "Fred" };
// PersonType is {name: string}
type PersonType = ReturnOf<typeof person>
It would be nice to enforce ReturnOf only accept function as type:
type ReturnOf<F extends (...args: any[]) => any> = F extends (...args: any[]) => infer RT ? RT: F
Another way to write ReturnOf<F> is:
type ReturnOf<F extends (...args: any[]) => any> = F extends {(...args: any[]): infer RT} ? RT: never
// F extends {(): any}: F should be any callable
// F extends {(...args: any[]): infer RT}: the return type of the callable should be RT
// F extends {(...args: any[]): infer RT} ? RT: never : if the return type is true, then return RT otherwise return never
----- Difficult----
/**
* Tips:
* 1. handle from less restricted type
* `Expect<Equal<Split<string, "whatever">, string[]>>`
* Here Split<string, "whatever">, string is most generic type
* let a: string extends 'abc' ? true: false; // false, general -> specific
* let a: 'abc' extends string ? true: false; // true, specific -> general
* only when: let a: string extends string ? true: false; // true, general -> general
*
* 2. Then "", empty string is more specific than `string`
* 3. You can use multi infer
* S extends `${infer FIRST}${SEP}${infer REST}`
* For example: Split<['hello world'], " ">
* S: hello world
* SEP: " "
* ${infer FIRST}${SEP}${infer REST}
* |. |. |
* hello. " " world
* FIRST is hello
* SEP is " "
* REST is world
* 4. Calling recurivly: ? [FIRST, ...Split<REST, SEP>]
*/
type Split<S extends string, SEP extends string> = string extends S
? string[]
: S extends ""
? SEP extends "" ? []: [""]
: S extends `${infer FIRST}${SEP}${infer REST}`
? [FIRST, ...Split<REST, SEP>]
:[S];
// Tests
type cases = [
Expect<
Equal<
Split<"Hi! How are you?", "z">,
["Hi! How are you?"]
>
>,
Expect<
Equal<
Split<"Hi! How are you?", " ">,
["Hi!", "How", "are", "you?"]
>
>,
Expect<
Equal<
Split<"Hi! How are you?", "">,
[
"H",
"i",
"!",
" ",
"H",
"o",
"w",
" ",
"a",
"r",
"e",
" ",
"y",
"o",
"u",
"?"
]
>
>,
Expect<Equal<Split<"", "">, []>>,
Expect<Equal<Split<"", "z">, [""]>>,
Expect<Equal<Split<string, "whatever">, string[]>>
]