Skip to content

Commit

Permalink
Add IsX/IfX types for any/never/unknown (#564)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
tommy-mitchell and sindresorhus committed Apr 5, 2023
1 parent 5374588 commit 4045737
Show file tree
Hide file tree
Showing 20 changed files with 368 additions and 27 deletions.
6 changes: 6 additions & 0 deletions index.d.ts
Expand Up @@ -85,6 +85,12 @@ export type {
IsBooleanLiteral,
IsSymbolLiteral,
} from './source/is-literal';
export type {IsAny} from './source/is-any';
export type {IfAny} from './source/if-any';
export type {IsNever} from './source/is-never';
export type {IfNever} from './source/if-never';
export type {IsUnknown} from './source/is-unknown';
export type {IfUnknown} from './source/if-unknown';

// Template literal types
export type {CamelCase} from './source/camel-case';
Expand Down
33 changes: 32 additions & 1 deletion readme.md
Expand Up @@ -174,12 +174,43 @@ Click the type names for complete docs.
- [`HasRequiredKeys`](source/has-required-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any required fields.
- [`Spread`](source/spread.d.ts) - Mimic the type inferred by TypeScript when merging two objects or two arrays/tuples using the spread syntax.
- [`IsEqual`](source/is-equal.d.ts) - Returns a boolean for whether the two given types are equal.
- [`TaggedUnion`](source/tagged-union.d.ts) - Create a union of types that share a common discriminant property.

### Type Guard

#### `IsType` vs. `IfType`

For every `IsT` type (e.g. `IsAny`), there is an associated `IfT` type that can help simplify conditional types. While the `IsT` types return a `boolean`, the `IfT` types act like an `If`/`Else` - they resolve to the given `TypeIfT` or `TypeIfNotT` depending on whether `IsX` is `true` or not. By default, `IfT` returns a `boolean`:

```ts
type IfAny<T, TypeIfAny = true, TypeIfNotAny = false> = (
IsAny<T> extends true ? TypeIfAny : TypeIfNotAny
);
```

#### Usage

```ts
import type {IsAny, IfAny} from 'type-fest';

type ShouldBeTrue = IsAny<any> extends true ? true : false;
//=> true

type ShouldBeFalse = IfAny<'not any'>;
//=> false

type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
//=> 'never'
```

- [`IsLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsStringLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsNumericLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsBooleanLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsSymbolLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`TaggedUnion`](source/tagged-union.d.ts) - Create a union of types that share a common discriminant property.
- [`IsAny`](source/is-any.d.ts) - Returns a boolean for whether the given type is `any`. (Conditional version: [`IfAny`](source/if-any.d.ts).)
- [`IsNever`](source/is-never.d.ts) - Returns a boolean for whether the given type is `never`. (Conditional version: [`IfNever`](source/if-never.d.ts).)
- [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`. (Conditional version: [`IfUnknown`](source/if-unknown.d.ts).)

### JSON

Expand Down
24 changes: 24 additions & 0 deletions source/if-any.d.ts
@@ -0,0 +1,24 @@
import type {IsAny} from './is-any';

/**
An if-else-like type that resolves depending on whether the given type is `any`.
@see {@link IsAny}
@example
```
import type {IfAny} from 'type-fest';
type ShouldBeTrue = IfAny<any>;
//=> true
type ShouldBeBar = IfAny<'not any', 'foo', 'bar'>;
//=> 'bar'
```
@category Type Guard
@category Utilities
*/
export type IfAny<T, TypeIfAny = true, TypeIfNotAny = false> = (
IsAny<T> extends true ? TypeIfAny : TypeIfNotAny
);
24 changes: 24 additions & 0 deletions source/if-never.d.ts
@@ -0,0 +1,24 @@
import type {IsNever} from './is-never';

/**
An if-else-like type that resolves depending on whether the given type is `never`.
@see {@link IsNever}
@example
```
import type {IfNever} from 'type-fest';
type ShouldBeTrue = IfNever<never>;
//=> true
type ShouldBeBar = IfNever<'not never', 'foo', 'bar'>;
//=> 'bar'
```
@category Type Guard
@category Utilities
*/
export type IfNever<T, TypeIfNever = true, TypeIfNotNever = false> = (
IsNever<T> extends true ? TypeIfNever : TypeIfNotNever
);
24 changes: 24 additions & 0 deletions source/if-unknown.d.ts
@@ -0,0 +1,24 @@
import type {IsUnknown} from './is-unknown';

/**
An if-else-like type that resolves depending on whether the given type is `unknown`.
@see {@link IsUnknown}
@example
```
import type {IfUnknown} from 'type-fest';
type ShouldBeTrue = IfUnknown<unknown>;
//=> true
type ShouldBeBar = IfUnknown<'not unknown', 'foo', 'bar'>;
//=> 'bar'
```
@category Type Guard
@category Utilities
*/
export type IfUnknown<T, TypeIfUnknown = true, TypeIfNotUnknown = false> = (
IsUnknown<T> extends true ? TypeIfUnknown : TypeIfNotUnknown
);
23 changes: 6 additions & 17 deletions source/internal.d.ts
@@ -1,6 +1,7 @@
import type {Primitive} from './primitive';
import type {Simplify} from './simplify';
import type {Trim} from './trim';
import type {IsAny} from './is-any';

/**
Infer the length of the given array `<T>`.
Expand Down Expand Up @@ -158,23 +159,6 @@ export type IsNumeric<T extends string> = T extends `${number}`
: false
: false;

/**
Returns a boolean for whether the the type is `any`.
@link https://stackoverflow.com/a/49928360/1490091
*/
export type IsAny<T> = 0 extends 1 & T ? true : false;

/**
Returns a boolean for whether the the type is `never`.
*/
export type IsNever<T> = [T] extends [never] ? true : false;

/**
Returns a boolean for whether the the type is `unknown`.
*/
export type IsUnknown<T> = IsNever<T> extends false ? T extends unknown ? unknown extends T ? IsAny<T> extends false ? true : false : false : false : false;

/**
For an object T, if it has any properties that are a union with `undefined`, make those into optional properties instead.
Expand Down Expand Up @@ -262,3 +246,8 @@ export type HasMultipleCallSignatures<T extends (...arguments: any[]) => unknown
Returns a boolean for whether the given `boolean` is not `false`.
*/
export type IsNotFalse<T extends boolean> = [T] extends [false] ? false : true;

/**
Returns a boolean for whether the given type is `null`.
*/
export type IsNull<T> = [T] extends [null] ? true : false;
29 changes: 29 additions & 0 deletions source/is-any.d.ts
@@ -0,0 +1,29 @@
/**
Returns a boolean for whether the given type is `any`.
@link https://stackoverflow.com/a/49928360/1490091
Useful in type utilities, such as disallowing `any`s to be passed to a function.
@example
```
import type {IsAny} from 'type-fest';
const typedObject = {a: 1, b: 2} as const;
const anyObject: any = {a: 1, b: 2};
function get<O extends (IsAny<O> extends true ? {} : Record<string, number>), K extends keyof O = keyof O>(obj: O, key: K) {
return obj[key];
}
const typedA = get(typedObject, 'a');
//=> 1
const anyA = get(anyObject, 'a');
//=> any
```
@category Type Guard
@category Utilities
*/
export type IsAny<T> = 0 extends 1 & T ? true : false;
1 change: 1 addition & 0 deletions source/is-equal.d.ts
Expand Up @@ -21,6 +21,7 @@ type Includes<Value extends readonly any[], Item> =
: false;
```
@category Type Guard
@category Utilities
*/
export type IsEqual<A, B> =
Expand Down
13 changes: 7 additions & 6 deletions source/is-literal.d.ts
@@ -1,6 +1,7 @@
import type {Primitive} from './primitive';
import type {Numeric} from './numeric';
import type {IsNever, IsNotFalse} from './internal';
import type {IsNotFalse} from './internal';
import type {IsNever} from './is-never';

/**
Returns a boolean for whether the given type `T` is the specified `LiteralType`.
Expand Down Expand Up @@ -77,8 +78,8 @@ const output = capitalize('hello, world!');
//=> 'Hello, world!'
```
@category Utilities
@category Type Guard
@category Utilities
*/
export type IsStringLiteral<T> = LiteralCheck<T, string>;

Expand Down Expand Up @@ -125,8 +126,8 @@ endsWith('abc123', end);
//=> boolean
```
@category Utilities
@category Type Guard
@category Utilities
*/
export type IsNumericLiteral<T> = LiteralChecks<T, Numeric>;

Expand Down Expand Up @@ -165,8 +166,8 @@ const eitherId = getId({asString: runtimeBoolean});
//=> number | string
```
@category Utilities
@category Type Guard
@category Utilities
*/
export type IsBooleanLiteral<T> = LiteralCheck<T, boolean>;

Expand Down Expand Up @@ -200,8 +201,8 @@ get({[symbolValue]: 1} as const, symbolValue);
//=> number
```
@category Utilities
@category Type Guard
@category Utilities
*/
export type IsSymbolLiteral<T> = LiteralCheck<T, symbol>;

Expand Down Expand Up @@ -246,7 +247,7 @@ stripLeading(str, 'abc');
//=> string
```
@category Utilities
@category Type Guard
@category Utilities
*/
export type IsLiteral<T extends Primitive> = IsNotFalse<IsLiteralUnion<T>>;
49 changes: 49 additions & 0 deletions source/is-never.d.ts
@@ -0,0 +1,49 @@
/**
Returns a boolean for whether the given type is `never`.
@link https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
@link https://stackoverflow.com/a/53984913/10292952
@link https://www.zhenghao.io/posts/ts-never
Useful in type utilities, such as checking if something does not occur.
@example
```
import type {IsNever} from 'type-fest';
type And<A, B> =
A extends true
? B extends true
? true
: false
: false;
// https://github.com/andnp/SimplyTyped/blob/master/src/types/strings.ts
type AreStringsEqual<A extends string, B extends string> =
And<
IsNever<Exclude<A, B>> extends true ? true : false,
IsNever<Exclude<B, A>> extends true ? true : false
>;
type EndIfEqual<I extends string, O extends string> =
AreStringsEqual<I, O> extends true
? never
: void;
function endIfEqual<I extends string, O extends string>(input: I, output: O): EndIfEqual<I, O> {
if (input === output) {
process.exit(0);
}
}
endIfEqual('abc', 'abc');
//=> never
endIfEqual('abc', '123');
//=> void
```
@category Type Guard
@category Utilities
*/
export type IsNever<T> = [T] extends [never] ? true : false;
52 changes: 52 additions & 0 deletions source/is-unknown.d.ts
@@ -0,0 +1,52 @@
import type {IsNull} from './internal';

/**
Returns a boolean for whether the given type is `unknown`.
@link https://github.com/dsherret/conditional-type-checks/pull/16
Useful in type utilities, such as when dealing with unknown data from API calls.
@example
```
import type {IsUnknown} from 'type-fest';
// https://github.com/pajecawav/tiny-global-store/blob/master/src/index.ts
type Action<TState, TPayload = void> =
IsUnknown<TPayload> extends true
? (state: TState) => TState,
: (state: TState, payload: TPayload) => TState;
class Store<TState> {
constructor(private state: TState) {}
execute<TPayload = void>(action: Action<TState, TPayload>, payload?: TPayload): TState {
this.state = action(this.state, payload);
return this.state;
}
// ... other methods
}
const store = new Store({value: 1});
declare const someExternalData: unknown;
store.execute(state => ({value: state.value + 1}));
//=> `TPayload` is `void`
store.execute((state, payload) => ({value: state.value + payload}), 5);
//=> `TPayload` is `5`
store.execute((state, payload) => ({value: state.value + payload}), someExternalData);
//=> Errors: `action` is `(state: TState) => TState`
```
@category Utilities
*/
export type IsUnknown<T> = (
unknown extends T // `T` can be `unknown` or `any`
? IsNull<T> extends false // `any` can be `null`, but `unknown` can't be
? true
: false
: false
);
3 changes: 2 additions & 1 deletion source/jsonify.d.ts
@@ -1,8 +1,9 @@
import type {JsonPrimitive, JsonValue} from './basic';
import type {EmptyObject} from './empty-object';
import type {IsAny, UndefinedToOptional} from './internal';
import type {UndefinedToOptional} from './internal';
import type {NegativeInfinity, PositiveInfinity} from './numeric';
import type {TypedArray} from './typed-array';
import type {IsAny} from './is-any';

// Note: The return value has to be `any` and not `unknown` so it can match `void`.
type NotJsonable = ((...arguments_: any[]) => any) | undefined | symbol;
Expand Down
2 changes: 1 addition & 1 deletion source/set-return-type.d.ts
@@ -1,4 +1,4 @@
import type {IsUnknown} from './internal';
import type {IsUnknown} from './is-unknown';

/**
Create a function type with a return type of your choice and the same parameters as the given function type.
Expand Down

0 comments on commit 4045737

Please sign in to comment.