Created
July 5, 2023 22:17
-
-
Save webstrand/e5098db3fb5cfed841e9d4eda1ba3b19 to your computer and use it in GitHub Desktop.
Type checking functions
This file contains 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
/** | |
* Type alias resolves to `True` if and only if `U` is the same as `V`, otherwise it resolves to `False`. | |
* @typeparam U - An arbitrary type | |
* @typeparam V - An arbitrary type | |
* @typeparam True - Production when `U` is the same as `V` | |
* @typeparam False - Production when `U` is not the same as `V` | |
*/ | |
export type Exact<U, V, True = true, False = false> = | |
{ <_>(): _ extends U ? 1 : 0 } extends { <_>(): _ extends V ? 1 : 0 } | |
? True | |
: False; | |
/** | |
* Type alias resolves to `true` if and only if `L` is the same as `R`. | |
* | |
* Left or right type does not matter, only affects error messages. | |
* @typeparam L - Left arbitrary type | |
* @typeparam R - Right arbitrary type | |
*/ | |
export type ExactTrue<L, R> = | |
0 extends (1 & L) | |
? 0 extends (1 & R) | |
? true | |
: "L is any but R is NOT any" & { L?: L, R?: R } | |
: 0 extends (1 & R) | |
? "L is NOT any but R is any" & { L?: L, R?: R } | |
: [L] extends [R] | |
? [R] extends [L] | |
? { <_>(): _ extends L ? 1 : 0 } extends { <_>(): _ extends R ? 1 : 0 } | |
? true | |
: "Variance between L and R" & { L?: L, R?: R } | |
: "R is not assignable to L" & { L?: L, R?: R } | |
: "L is not assignable to R" & { L?: L, R?: R }; | |
/** | |
* Type alias resolves to `false` if and only if `L` is not the same as `R`. | |
* | |
* Left or right type does not matter, only affects error messages. | |
* @typeparam L - Left arbitrary type | |
* @typeparam R - Right arbitrary type | |
*/ | |
export type ExactFalse<L, R> = | |
0 extends (1 & L) | |
? 0 extends (1 & R) | |
? "L is any and R is any" & { L?: L, R?: R } | |
: false | |
: 0 extends (1 & R) | |
? false | |
: [L] extends [R] | |
? [R] extends [L] | |
? { <_>(): _ extends L ? 1 : 0 } extends { <_>(): _ extends R ? 1 : 0 } | |
? "L is the same as R" & { L?: L, R?: R } | |
: false | |
: false | |
: false; | |
/** Unique symbol to use as placeholder on inferrence failure */ | |
const Unspecified = Symbol(); | |
/** Unique symbol to use as placeholder on inferrence failure */ | |
type Unspecified = typeof Unspecified; | |
/** | |
* Utility type produces an error message when inferrence fails and resolves to | |
* `Unspecified` | |
*/ | |
type Spec<Name extends "L" | "R", LR, Return> = | |
Unspecified extends LR | |
? `type parameter ${Name} must be specified or inferred` | |
: Return; | |
/** | |
* Compare `L` and `R` for equivalence, partial application for partial inferrence. | |
* @param v - Optionally specify `l` to infer `L`. | |
* @template L - May be specified manually, or inferred from function parameter `l` | |
*/ | |
export function Exact<L = Unspecified>(l?: L): Spec<"L", L, { | |
/** | |
* @param v - `true` when `L` is the same as `R` | |
* @template R - Must be specified, use a function parameter to infer | |
*/ | |
<R = Unspecified>(v: Spec<"L", L, ExactTrue<L, R>>): void; | |
/** | |
* @param r - infers `R` from the function parameter | |
* @param v - `true` when `L` is the same as `R` | |
* @template R - inferred from function parameter `r` | |
*/ | |
<R>(r: R, v: ExactTrue<L, R>): void; | |
/** | |
* @param v - `false` when `L` is not the same as `R` | |
* @template R - Must be specified, use a function parameter to infer | |
*/ | |
<R = Unspecified>(v: Spec<"R", R, ExactFalse<L, R>>): void; | |
/** | |
* @param r - infers `R` from the function parameter | |
* @param v - `false` when `L` is not the same as `R` | |
* @template R - inferred from function parameter `r` | |
*/ | |
<R>(r: R, v: ExactFalse<L, R>): void; | |
}>; | |
/** | |
* Compare `L` and `R` for equivalence, manually bind type parameter `L` and | |
* `R`. | |
* @param v - `true` when `L` is the same as `R` | |
* @template L - Must be specified, use a function parameter to infer | |
* @template R - Must be specified, use a function parameter to infer | |
*/ | |
export function Exact<L = Unspecified, R = Unspecified>( | |
v: Spec<"L", L, Spec<"R", R, ExactTrue<L, R>>> | |
): void; | |
/** | |
* Compare `L` and `R` for equivalence, manually bind type parameter `L` and | |
* `R`. | |
* @param v - `false` when `L` is not the same as `R` | |
* @template L - Must be specified, use a function parameter to infer | |
* @template R - Must be specified, use a function parameter to infer | |
*/ | |
export function Exact<L = Unspecified, R = Unspecified>( | |
v: Spec<"L", L, Spec<"R", R, ExactFalse<L, R>>> | |
): void; | |
/** | |
* Compare `L` and `R` for equivalence, infers type parameter `L` and `R`. | |
* @param l - infers `L` from the function parameter | |
* @param r - infers `R` from the function parameter | |
* @param v - `true` when `L` is the same as `R` | |
* @template L - inferred from function parameter `l` | |
* @template R - inferred from function parameter `r` | |
*/ | |
export function Exact<L, R>(l: L, r: R, v: ExactTrue<L, R>): void; | |
/** | |
* Compare `L` and `R` for equivalence, infers type parameter `L` and `R`. | |
* @param l - infers `L` from the function parameter | |
* @param r - infers `R` from the function parameter | |
* @param v - `false` when `L` is not the same as `R` | |
* @template L - inferred from function parameter `l` | |
* @template R - inferred from function parameter `r` | |
*/ | |
export function Exact<L, R>(l: L, r: R, v: ExactFalse<L, R>): void; | |
/** @private stub implementation */ | |
export function Exact(): any { | |
return Exact; | |
} | |
// As far as TS knows, Exact() sometimes resolves to a string. Prevent | |
// misuse of Exact as a string at runtime. | |
Object.defineProperties(Exact, { | |
[Symbol.toPrimitive]: { value() { throw "Exact may not be coerced" }, enumerable: false }, | |
valueOf: { value() { throw "Exact may not be coerced" }, enumerable: false } | |
}); | |
/** | |
* Compare `L` and `R` for assignability from `L` onto `R`. | |
* @param l - Optionally specify `l` to infer `L`. | |
* @template L - Source type, assigned to R. | |
*/ | |
export function Assignable<L = Unspecified>(l?: L): Spec<"L", L, { | |
/** | |
* @param v - `true` when `L` is assignable to `R`, false otherwise. | |
* @template R - Must be specified, use a function parameter to infer | |
*/ | |
<R = Unspecified>(v: Spec<"R", R, [L] extends [R] ? true : false>): void; | |
/** | |
* @param r - infers `R` from the function parameter | |
* @param v - `true` when `L` is assignable to `R`, false otherwise. | |
*/ | |
<R>(r: R, v: [L] extends [R] ? true : false): void; | |
}> | |
/** | |
* Compare `L` and `R` for assignability from `L` onto `R`, manually bind type | |
* parameter `L` and `R`. | |
* @param v - `true` when L is assignable to R. | |
* @template L - Source type, assigned to R. | |
* @template R - Destination type, assigned from L. | |
*/ | |
export function Assignable<L = Unspecified, R = Unspecified>( | |
v: Spec<"L", L, Spec<"R", R, [L] extends [R] ? true : false>> | |
): void; | |
/** | |
* Compare `L` and `R` for assignability from `L` onto `R`, inferrs type | |
* parameter `L` and `R`. | |
* @param v - `true` when L is assignable to R. | |
* @param r - infers `R` from the function parameter | |
* @template L - Source type, assigned to R. | |
* @template R - Destination type, assigned from L. | |
*/ | |
export function Assignable<L, R>( | |
l: L, | |
r: R, | |
v: [L] extends [R] ? true : false | |
): void; | |
/** @private stub implementation */ | |
export function Assignable(): any { | |
return Assignable; | |
} | |
// As far as TS knows, Assignable() sometimes resolves to a string. Prevent | |
// misuse of Assignable as a string at runtime. | |
Object.defineProperties(Assignable, { | |
[Symbol.toPrimitive]: { | |
value() { throw "Assignable may not be coerced" }, | |
enumerable: false | |
}, | |
valueOf: { | |
value() { throw "Assignable may not be coerced" }, | |
enumerable: false | |
} | |
}); | |
// from https://gist.github.com/webstrand/b0f79ef6ed37839d1432466fe8ddbc1a |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment