Skip to content

Instantly share code, notes, and snippets.

@webstrand
Created July 5, 2023 22:17
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 webstrand/e5098db3fb5cfed841e9d4eda1ba3b19 to your computer and use it in GitHub Desktop.
Save webstrand/e5098db3fb5cfed841e9d4eda1ba3b19 to your computer and use it in GitHub Desktop.
Type checking functions
/**
* 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