Last active
May 30, 2025 20:14
-
-
Save Fasteroid/1cf4a20bb6ebc7be1d83ed265c8b22b1 to your computer and use it in GitHub Desktop.
Some random, probably inadvised things you can do with TypeScript's type system.
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
/** | |
* WARNING! | |
* Just because you can doesn't mean you should. | |
* | |
* It's not very hard to create types with exponential (or worse!) time complexity. | |
* These can create serious issues at build time, such as running out of memory. | |
* Use caution when doing metaprogramming like this. Nothing is truly free. | |
* | |
* This file is best viewed in an environment with intellisense. | |
*/ | |
/** | |
* Generates a tuple of *{@link T}* that is *{@link N}* elements long. | |
* @concepts https://stackoverflow.com/questions/52489261/can-i-define-an-n-length-tuple-type | |
*/ | |
type TupleOfLength<N extends number, T = any, RETURN extends T[] = []> = | |
RETURN['length'] extends N ? // if we reach the desired length | |
RETURN // return RETURN | |
: | |
TupleOfLength<N, T, [T, ...RETURN]> // else try again with a tuple 1 element longer | |
// | |
/** | |
* Generates a tuple [0 ... *{@link N}*-1] | |
* @concepts {@linkcode TupleOfLength} | |
*/ | |
type AscendingTuple<N extends number, RETURN extends number[] = []> = | |
RETURN['length'] extends N ? | |
RETURN | |
: | |
AscendingTuple<N, [ | |
...RETURN, // flip the order of these | |
RETURN['length'] // to make it descending | |
]> | |
// | |
/** | |
* *{@link A}* + *{@link B}* | |
* #### Caveats: | |
* - no negative numbers | |
* - O(n) complexity | |
*/ | |
type Add<A extends number, B extends number> = [...TupleOfLength<A>, ...TupleOfLength<B>]['length'] | |
// | |
/** | |
* Gets the first element of a tuple. | |
*/ | |
type FirstOf<TUPLE extends unknown[]> = | |
TUPLE extends [infer FIRST, ...infer REMAINING] ? // flip the order to get the last | |
FIRST // use REMAINING to get the rest instead | |
: | |
never | |
// | |
/** | |
* Finds the sum of a tuple of numbers. | |
* Note that now, working with tuples directly for mathematical operations is now more convenient than using actual numbers. | |
* #### Caveats: | |
* - O(n^2) complexity | |
* @concepts {@linkcode FirstOf}, {@linkcode TupleOfLength} | |
*/ | |
type Sum<NUMS extends number[]> = _Sum<NUMS>['length'] | |
type _Sum<NUMS extends number[], RETURN extends unknown[] = []> = | |
NUMS['length'] extends 0 ? | |
RETURN | |
: | |
NUMS extends [infer FIRST extends number, ...infer REMAINING extends number[]] ? | |
_Sum<REMAINING, [...RETURN, ...TupleOfLength<FIRST>]> | |
: | |
never | |
// | |
/** | |
* Keys of *{@link T}* which hold *{@link KIND}* | |
* @concepts https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types | |
*/ | |
type KeysHolding<T, KIND = any> = keyof // keys of the following: | |
{ [K in keyof T // a type with the keys of T | |
as ( T[K] extends KIND ? // but try to cast those keys to KIND | |
K | |
: | |
never // producing 'never' in the 'as' clause filters the key out | |
) | |
]: T[K] // use the original value types of T where applicable | |
} | |
// | |
/** | |
* A precursor to converting a tuple to a mapped type. | |
* This does it for the last item of the tuple only. | |
* @concepts {@linkcode FirstOf} | |
*/ | |
type ObjectWithLastKeyOf<TUPLE extends unknown[]> = | |
TUPLE extends [...infer REMAINING, infer LAST] ? | |
{ [K in TUPLE['length']]: LAST } // 'in' is similar to '==' here | |
: | |
never | |
// | |
type _TupleToMap< | |
STACK extends { | |
tup: unknown[] // the tuple to iterate | |
map: { [i: number]: unknown } // the map to return | |
}, | |
> = | |
STACK['tup'] extends [...infer REST, infer LAST] ? // pop LAST off `tup` if we can | |
_TupleToMap<{ | |
tup: REST, // pass the remaining to the next iteration | |
map: STACK['map'] & { // the previous iteration's map... | |
[K in STACK['tup']['length']]: LAST // ...plus the element we just popped from `tup` | |
} | |
}> | |
: | |
STACK['map'] // we couldn't pop, so we must be done | |
// | |
/** | |
* Converts a tuple to a mapped type with the same keys. | |
* ### Caveats | |
* - O(n) complexity | |
* @example ```ts | |
* type From = [1, "two", {value: 3}] | |
* type To = { | |
* 1: 1, | |
* 2: "two", | |
* 3: {value: 3} | |
* } | |
* ``` | |
* @concepts {@linkcode FirstOf}, {@linkcode ObjectWithLastKeyOf} | |
*/ | |
type TupleToMap<TUPLE extends unknown[]> = _TupleToMap<{ | |
tup: TUPLE, | |
map: {} | |
}> | |
// WARNING: The following type is actually very useful! | |
/** | |
* True if T is a positive number, false otherwise. | |
* @author Fasteroid | |
*/ | |
type Positive<T extends number> = `${T}` extends `-${number}` ? false : true; | |
/** | |
* Checks if a type is `never`... which is harder than you think it is! | |
* @concepts https://www.webdevtutor.net/blog/typescript-check-if-type-is-never | |
*/ | |
type IsNever<T> = [T] extends [never] ? true : false; | |
// Here's an approach for adding sidechannels to types for things like metadata, etc. | |
// I have no idea if this is a useful technique | |
class _name<T> { private _name?: }; | |
type NamelessThing = { what: string }; | |
type NamedThing = NamelessThing & _name<"Thing">; | |
type ThingName = NamedThing extends _name<infer T> ? T : never; // "Thing" | |
/** | |
* If both types are known, ensures they match, otherwise returns as much as is known. | |
* | |
* This can get you out of tricky binds where TypeScript's default inferencing is too strict. | |
*/ | |
type SoftMatch<A, B> = | |
[unknown] extends [A] ? // A is unknown | |
[unknown] extends [B] ? // B is unknown | |
unknown | |
: // B is known | |
B | |
: // A is known | |
[unknown] extends [B] ? // B is unknown | |
A | |
: // B is known | |
B extends A ? A extends B ? A : never : never // assert they are the same | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment