Last active
November 7, 2018 12:20
-
-
Save piscisaureus/a4fa24a16eb12663472a62cc2b0b602f to your computer and use it in GitHub Desktop.
Typescript left spread
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A typescript helper type that lets you define functions with rest spread | |
// parameters at the beginning, like: | |
// | |
// `function cool(...many: string[], must_have: SomeObject, extra?: boolean)`; | |
// | |
// See test.ts for an example on how to use it. | |
export type Args = Array<unknown>; | |
// prettier-ignore | |
export type LeftSpread< | |
Spread, // Type of the rest parameter to spread. | |
End extends Args, // Tuple of the parameter types that come afer the spread. | |
T extends Args // Actual type of ...args passed to function. | |
> = | |
SpreadHelper<Spread, End, T> & | |
UnionHelper<Spread, End> & | |
T; | |
// prettier-ignore | |
type SpreadHelper< | |
Spread, | |
End extends Args, | |
T extends Args, | |
Acc extends Args = End, | |
Forks = never | |
> = { | |
"done": Acc | Forks, | |
"push": SpreadHelper<Spread, End, TailOf<T>, Prepend<Spread, Acc>, Forks>, | |
"init": SpreadHelper<Spread, TailOf<End>, TailOf<T>, Acc, Forks>, | |
"fork": SpreadHelper<Spread, TailOf<End>, TailOf<T>, Acc, | |
SpreadHelper<Spread, TailOf<End>, T, Acc, Forks>> | |
}[IfEmpty<End, | |
IfEmpty<T, "done", "push">, | |
IfNotEmpty<End, "init", "fork">> | |
]; | |
// prettier-ignore | |
type TailOf<T extends Args> = | |
((...all: T) => void) extends ((first: infer _, ...rest: infer R) => void) | |
? R | |
: never; | |
// prettier-ignore | |
type Prepend<S, T extends Args> = | |
((first: S, ...rest: T) => void) extends ((...all: infer R) => void) | |
? R: | |
never; | |
// Note that a type might be neither Empty nor NotEmpty, e.g. [string?]. | |
type IfEmpty<T extends Args, True, False> = T extends { length: 0 } | |
? True | |
: False; | |
type IfNotEmpty<T extends Args, True, False> = T extends { 0: unknown } | |
? True | |
: False; | |
type UnionHelper<Spread, End extends Args> = Array< | |
Spread | Required<End>[number] | |
>; | |
type Required<T> = { [P in keyof T]-?: T[P] }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The function we're testing with would have the following signature, if | |
// typescript supported this pattern natively. Note that the last parameter | |
// is optional; the return type is irrelevant. | |
// | |
// function fun( | |
// ...restArgs: string[], | |
// someBool: boolean, | |
// someObj?: SomeObj | |
// ) {} | |
import { Args, LeftSpread } from "./leftspread"; | |
type SomeFn = () => {}; | |
interface SomeObj { | |
ok: string; | |
} | |
function fun<T extends Args>( | |
...args: LeftSpread<string, [boolean, SomeObj?], T> | |
): typeof args { | |
return args; | |
} | |
// Should PASS (no typescript error): | |
fun(true); | |
fun(false, { ok: "yes" }); | |
fun("abc", false); | |
fun("abc", false, { ok: "yes" }); | |
fun("abc", "def", true); | |
fun("abc", "def", true, { ok: "yes" }); | |
fun("abc", "def", "ghi", true); | |
fun("abc", "def", "ghi", true, { ok: "yes" }); | |
// Should FAIL (typescript error). | |
fun(); | |
fun(undefined, "string", true); | |
fun("string", undefined, "string", true); | |
fun("abc"); | |
fun("abc", undefined); | |
fun(true, undefined); | |
fun("abc", false, undefined); | |
fun("abc", false, { ok: "yes" }, undefined); | |
fun(false, { ok: "yes" }, 33); | |
fun("abc", false, null); | |
fun("abc", "def", false, undefined); | |
fun("abc", false, { ok: -9000 }); | |
fun("abc", "def", { ok: "yes" }, true); | |
fun("abc", "def", "ghi"); | |
fun("abc", "def", "ghi", { ok: "yes" }); | |
fun("abc", "def", "ghi", false, { ok: "yes" }, undefined); | |
fun(-1, "a", "b", "c", "d", "e", "f", true, { ok: "yes" }); | |
fun("a", "b", "c", "d", "e", "f", true, { ok: "yes" }, -1); | |
// function cute(...restArgs: number[], arrayOfNumbers: number[]) | |
function cute<T extends Args>( | |
...args: LeftSpread<number, [number[]], T> | |
): typeof args { | |
return args; | |
} | |
// Should PASS (no typescript error): | |
cute([]); | |
cute([89, 90]); | |
cute(0, [89, 90]); | |
cute(-1, 2, 3, 4, 5, [99, 100]); | |
cute(-0, 2, 3, 4, 5, 6, 7, 8, 9, 10, []); | |
// Should FAIL (typescript error). | |
cute(); | |
cute(undefined); | |
cute(0, 1, 2, null, [4, 5]); | |
cute(0, 1, 2, 3, "bla", [1]); | |
cute(0, 1, 2, 3, ["1", "2"]); | |
cute(0, 1, 2, 3, [null]); | |
cute(0, 1, 2, 3, "0", []); | |
cute(0, 1, 2, 3, [], undefined); | |
cute(undefined, 1, 2, 3, 4, 5, []); | |
cute(0, 1, 2, 3, 4, 5, undefined, []); | |
// function cute(...bools: boolean[], f: () => void, s: string) | |
function primitively<T extends Args>( | |
...args: LeftSpread<boolean, [SomeFn, string], T> | |
): boolean | SomeFn | string { | |
// Should PASS (no typescript error): | |
const first: boolean | SomeFn | string = args[0]; | |
const secnd: boolean | SomeFn | string = args[1]; | |
const third: boolean | SomeFn | string = args[2]; | |
return first || secnd || third; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment