rivo
Version:
🤖 The ultimate library you need for composable type-level programming in TypeScript, powered by HKT.
377 lines (357 loc) • 10.4 kB
TypeScript
import type { IsTuple } from "./Ary/IsTuple";
import type { List } from "./List";
import type { Int, Nat } from "./Num";
import type { Ordering } from "./typeclass/Ord";
/**
* A type that represents a primitive value.
*/
export type Primitive = null | undefined | string | number | boolean | symbol | bigint;
/**
* Force casting `T` to `U`.
*
* **It is not recommended to use simply for eliminating type errors,
* as it reduced the ability of error recovery.**
*
* If you're sure an error won't happen but TS doesn't know,
* use `@ts-expect-error` instead.
*/
export type Cast<T, U> = T extends U ? T : U;
/*********************
* Lazied evaluation *
*********************/
/**
* Wrap `T` to make it lazily evaluated by TS.
*
* It is useful when creating self-referential union types, especially for implementing type
* classes.
*
* Unwrap the type by {@link Lazied$Get}.
*/
export interface Lazied<T = unknown> {
__lazy: T;
}
/**
* Get the wrapped type of lazied type `L`.
*
* @see {@link Lazied}
*/
export type Lazied$Get<L extends Lazied<unknown>> = L["__lazy"];
/**********************
* Boolean operations *
**********************/
/**
* Reverse a boolean value.
*/
export type Not<B extends boolean> =
boolean extends B ? boolean
: B extends true ? false
: true;
/**
* Return `true` if both `A` and `B` are `true`, otherwise `false`.
*/
export type And<A extends boolean, B extends boolean> =
A extends true ?
B extends true ?
true
: false
: false;
/**
* Return `true` if either `A` or `B` is `true`, otherwise `false`.
*/
export type Or<A extends boolean, B extends boolean> =
A extends true ? true
: B extends true ? true
: false;
/*************
* Assertion *
*************/
/**
* Assert that `Expect` extends `Type`.
*
* It is used to explicitly specify the `ReturnType` of a generic.
*
* There're several predefined aliases for common types like `AssertBool`, `AssertNum`, etc.
*
* @example
* ```typescript
* // The `Assert<readonly string[], ...>` below checks that
* // the return type should extend `readonly string[]`.
* type ToChars<S extends string> = Assert<
* readonly string[],
* S extends `${infer C}${infer R}`
* ? readonly [C, ...ToChars<R>]
* : readonly []
* >;
*
* // If you write something like this:
* type ToChars<S extends string> = Assert<
* readonly string[],
* S extends `${infer C}${infer R}` ? C : never
* //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* // Type 'S extends `${infer C}${infer R}` ? C : never' does not satisfy the constraint 'readonly string[]'.
* >;
* ```
*/
export type Assert<Type, Expect extends Type> = Expect;
/**
* @see {@link Assert}
*/
export type AssertUnknown<Expect> = Expect;
/**
* @see {@link Assert}
*/
export type AssertBool<Expect extends boolean> = Expect;
/**
* @see {@link Assert}
*/
export type AssertNum<Expect extends number> = Expect;
/**
* @see {@link Assert}
*/
export type AssertInt<Expect extends Int> = Expect;
/**
* @see {@link Assert}
*/
export type AssertNat<Expect extends Nat> = Expect;
/**
* @see {@link Assert}
*/
export type AssertOrdering<Expect extends Ordering> = Expect;
/**
* @see {@link Assert}
*/
export type AssertStr<Expect extends string> = Expect;
/**
* @see {@link Assert}
*/
export type AssertObj<Expect extends object> = Expect;
/****************
* Control flow *
****************/
/**
* If `Cond` is `true`, return `Then`, otherwise return `Else`.
*/
export type If<Cond extends boolean, Then, Else> = Cond extends true ? Then : Else;
/*************
* Predicate *
*************/
/**
* Check if `T` extends `U`.
*/
export type Extends<T, U> = T extends U ? true : false;
/**
* Check if `T` is `any`.
*/
export type IsAny<T> = 0 extends 1 & T ? true : false;
/**
* Check if `T` is `never`.
*/
export type IsNever<T> = [T] extends [never] ? true : false;
/**
* Check if `T` is `unknown`.
*/
export type IsUnknown<T> = unknown extends T ? true : false;
/**
* Check if all elements of a tuple of `boolean`s are `true`.
*
* @example
* ```typescript
* type R1 = All<[true, true, true]>;
* // ^?: true
* type R2 = All<[true, false, true]>;
* // ^?: false
* type R3 = All<[true, true, true, ...true[]]>;
* // ^?: true
* type R4 = All<[true, true, true, ...boolean[]]>;
* // ^?: boolean
* type R5 = All<[true, boolean, true]>;
* // ^?: boolean
* ```
*/
export type All<BS extends List<boolean>> =
BS extends BS ?
IsTuple<BS> extends false ?
BS[number] extends true ? true
: BS[number] extends false ? false
: boolean
: BS extends readonly [infer Head extends boolean, ...infer Tail extends List<boolean>] ?
boolean extends Head ? boolean
: Head extends false ? false
: All<Tail>
: true
: never;
/**
* Check if any element of a tuple of `boolean`s is `true`.
*
* @example
* ```typescript
* type R1 = Any<[false, false, false]>;
* // ^?: false
* type R2 = Any<[true, false, true]>;
* // ^?: false
* type R3 = Any<[true, false, true, ...true[]]>;
* // ^?: true
* type R4 = Any<[false, false, false, ...false[]]>;
* // ^?: false
* type R5 = Any<[true, true, true, ...boolean[]]>;
* // ^?: boolean
* type R6 = Any<[true, boolean, true]>;
* // ^?: true
* ```
*/
export type Any<BS extends List<boolean>> =
BS extends BS ?
IsTuple<BS> extends false ?
BS[number] extends false ? false
: BS[number] extends true ? true
: boolean
: BS extends readonly [infer Head extends boolean, ...infer Tail extends List<boolean>] ?
boolean extends Head ? boolean
: Head extends true ? true
: Any<Tail>
: false
: never;
/**
* Checks whether `T` exactly equals `U`.
*
* @example
* ```typescript
* type R1 = Eq<1, 1>;
* // ^?: true
* type R2 = Eq<1, number>;
* // ^?: false
* type R3 = Eq<1, 1 | 2>;
* // ^?: false
* ```
*/
export type Eq<T, U> =
(<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? true : false;
type IsLikelyClassType<T> =
T extends { prototype: unknown } ?
IsAny<T["prototype"]> extends true ?
false
: true
: false;
/**
* Broaden the type of `T` to a more general type.
*
* @example
* ```typescript
* type R1 = Broaden<42>;
* // ^?: number
* type R2 = Broaden<"foo" | false>;
* // ^?: string | boolean
* type R3 = Broaden<1336[]>;
* // ^?: number[]
* type R4 = Broaden<readonly [42, "foo"]>;
* // ^?: readonly (number | string)[]
* type R5 = Broaden<{ foo: 42; bar: "foo" }>;
* // ^?: { foo: number; bar: string; }
* type R6 = Broaden<Date>; // <- Internal types are not broadened
* // ^?: Date
* class Foo {
* constructor(public foo: 42, public bar: "foo") {}
* }
* type R7 = Broaden<typeof Foo>; // <- Class types are not broadened
* // ^?: typeof Foo
* ```
*/
export type Broaden<T> =
T extends string ? string
: T extends number ? number
: T extends boolean ? boolean
: T extends bigint ? bigint
: T extends Array<infer U> ? Array<Broaden<U>>
: T extends ReadonlyArray<infer U> ? ReadonlyArray<Broaden<U>>
: T extends object ?
// prettier-ignore
T extends ((...args: any) => any) | typeof globalThis | Date | RegExp | Error | Promise<any> | Map<any, any> | Set<any> | WeakMap<any, any> | WeakSet<any> | ArrayBuffer | DataView | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array | ArrayBufferView | ReadonlyMap<any, any> | ReadonlySet<any> ? T
: IsLikelyClassType<T> extends true ? T
: {
[K in keyof T]: T[K] extends (
string | number | boolean | bigint | ReadonlyArray<string | number | boolean | bigint>
) ?
Broaden<T[K]>
: T[K];
} extends infer R ?
Eq<R, T> extends true ?
T
: R
: never
: T;
/**
* Check if the given type `T` is the specified `LiteralType`.
*
* Modified from [type-fest](https://github.com/sindresorhus/type-fest/blob/main/source/is-literal.d.ts).
*
* @example
* ```typescript
* type R1 = LiteralCheck<1, number>;
* // ^?: true
* type R2 = LiteralCheck<number, number>;
* // ^?: false
* type R3 = LiteralCheck<1, string>;
* // ^?: false
* ```
*/
type LiteralCheck<T, LiteralType extends Primitive> =
IsNever<T> extends (
false // Must be wider than `never`
) ?
[T] extends (
[LiteralType & infer U] // Remove any branding
) ?
[U] extends (
[LiteralType] // Must be narrower than `LiteralType`
) ?
[LiteralType] extends (
[U] // Cannot be wider than `LiteralType`
) ?
false
: true
: false
: false
: false;
/**
* Check if the given type `T` is one of the specified literal types in `LiteralUnionType`.
*
* Modified from [type-fest](https://github.com/sindresorhus/type-fest/blob/main/source/is-literal.d.ts).
*
* @example
* ```typescript
* type R1 = LiteralChecks<1, number | bigint>;
* // ^?: true
* type R2 = LiteralChecks<1n, number | bigint>;
* // ^?: false
* type R3 = LiteralChecks<bigint, number | bigint>;
* // ^?: false
* ```
*/
type LiteralChecks<T, LiteralUnionType> =
// Conditional type to force union distribution.
// If `T` is none of the literal types in the union `LiteralUnionType`, then `LiteralCheck<T, LiteralType>` will evaluate to `false` for the whole union.
// If `T` is one of the literal types in the union, it will evaluate to `boolean` (i.e. `true | false`)
(LiteralUnionType extends Primitive ? LiteralCheck<T, LiteralUnionType> : never) extends false ?
false
: true;
export type IsStringLiteral<T> = LiteralCheck<T, string>;
export type IsNumberLiteral<T> = LiteralCheck<T, number>;
export type IsBigIntLiteral<T> = LiteralCheck<T, bigint>;
export type IsNumericLiteral<T> = LiteralChecks<T, number | bigint>;
export type IsBooleanLiteral<T> = LiteralCheck<T, boolean>;
export type IsSymbolLiteral<T> = LiteralCheck<T, symbol>;
export type IsPropertyKeyLiteral<T> = LiteralChecks<T, PropertyKey>;
/**
* Check if the given type `T` is a literal type.
*/
export type IsLiteral<T> =
[T] extends [Primitive] ?
IsLiteralUnion<T> extends false ?
false
: true
: false;
type IsLiteralUnion<T> =
| IsStringLiteral<T>
| IsNumericLiteral<T>
| IsBooleanLiteral<T>
| IsSymbolLiteral<T>;