Skip to content

Instantly share code, notes, and snippets.

@piscisaureus
Last active November 7, 2018 12:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save piscisaureus/a4fa24a16eb12663472a62cc2b0b602f to your computer and use it in GitHub Desktop.
Save piscisaureus/a4fa24a16eb12663472a62cc2b0b602f to your computer and use it in GitHub Desktop.
Typescript left spread
// 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] };
// 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