Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions source/internal/type.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type {If} from '../if.d.ts';
import type {IsAny} from '../is-any.d.ts';
import type {IsNever} from '../is-never.d.ts';
import type {Primitive} from '../primitive.d.ts';
import type {IsNever} from '../is-never.d.ts';
import type {IsAny} from '../is-any.d.ts';
import type {Or} from '../or.d.ts';
import type {If} from '../if.d.ts';

/**
Matches any primitive, `void`, `Date`, or `RegExp` value.
Expand All @@ -13,6 +14,13 @@ Matches non-recursive types.
*/
export type NonRecursiveType = BuiltIns | Function | (new (...arguments_: any[]) => unknown);

/**
* Checks if one type extends another. Note: this is not quite the same as `Left extends Right` because:
* 1. If either type is `never`, the result is `true` iff the other type is also `never`.
* 2. Types are wrapped in a 1-tuple so that union types are not distributed - instead we consider `string | number` to _not_ extend `number`. If we used `Left extends Right` directly you would get `Extends<string | number, number>` => `false | true` => `boolean`.
*/
export type Extends<Left, Right> = IsNever<Left> extends true ? IsNever<Right> : [Left] extends [Right] ? true : false;

/**
Returns a boolean for whether the two given types extends the base type.
*/
Expand Down Expand Up @@ -40,9 +48,19 @@ export type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknow
: false;

/**
Returns a boolean for whether the given `boolean` is not `false`.
Returns a boolean for whether the given `boolean` Union containe's `false`.
*/
export type IsNotFalse<T extends boolean> = [T] extends [false] ? false : true;
export type IsNotFalse<T extends boolean> = Not<IsFalse<T>>;

/**
Returns a boolean for whether the given `boolean` Union members are all `true`.
*/
export type IsTrue<T extends boolean> = Extends<T, true>;

/**
Returns a boolean for whether the given `boolean` Union members are all `false`.
*/
export type IsFalse<T extends boolean> = Extends<T, false>;

/**
Returns a boolean for whether the given type is primitive value or primitive type.
Expand All @@ -59,7 +77,7 @@ IsPrimitive<Object>
//=> false
```
*/
export type IsPrimitive<T> = [T] extends [Primitive] ? true : false;
export type IsPrimitive<T> = Extends<T, Primitive>;

/**
Returns a boolean for whether A is false.
Expand Down Expand Up @@ -99,3 +117,8 @@ type C = IfNotAnyOrNever<never, 'VALID', 'IS_ANY', 'IS_NEVER'>;
*/
export type IfNotAnyOrNever<T, IfNotAnyOrNever, IfAny = any, IfNever = never> =
If<IsAny<T>, IfAny, If<IsNever<T>, IfNever, IfNotAnyOrNever>>;

/**
Determines if a type is either `never` or `any`.
*/
export type IsAnyOrNever<T> = Or<IsAny<T>, IsNever<T>>;
35 changes: 35 additions & 0 deletions source/xor.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type {IsAnyOrNever, IsFalse, IsTrue} from './internal/type.d.ts';
import type {ApplyDefaultOptions} from './internal/object.d.ts';
import type {Includes} from './includes.d.ts';
import type {CountOf} from './count-of.d.ts';
import type {IsEqual} from './is-equal.d.ts';

type XorOptions = {
/**
Only match a specific number of trues.

@default 1 <= x <= length - 1
*/
strict?: number;
};

type DefaultXorOptions = {
strict: -1;
};

type Xor<A extends boolean, B extends boolean> = XorAll<[A, B]>;

type XorAll<T extends boolean[], Options extends XorOptions = {}> = (
ApplyDefaultOptions<XorOptions, DefaultXorOptions, Options> extends infer ResolvedOptions extends Required<XorOptions>
? IsAnyOrNever<T[number]> extends false
? Includes<T, boolean> extends false
? [IsFalse<T[number]> | IsTrue<T[number]>] extends [false]
? ResolvedOptions['strict'] extends infer TrueCount extends number
? TrueCount extends -1 ? true
: IsEqual<CountOf<T, true>, TrueCount>
: never
: false
: never
: never
: never
);
60 changes: 60 additions & 0 deletions test-d/xor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {expectType} from 'tsd';
import type {Xor, XorAll} from '../source/xor.d.ts';

declare const never: never;

// Xor
expectType<Xor<true, true>>(false);
expectType<Xor<true, false>>(true);
expectType<Xor<false, true>>(true);
expectType<Xor<false, false>>(false);
expectType<Xor<true, boolean>>(never);
expectType<Xor<false, boolean>>(never);
expectType<Xor<boolean, boolean>>(never);

expectType<Xor<boolean, never>>(never);
expectType<Xor<never, boolean>>(never);
expectType<Xor<false, never>>(false);
expectType<Xor<never, false>>(false);
expectType<Xor<never, true>>(false);
expectType<Xor<true, never>>(false);
expectType<Xor<never, never>>(never);

// XorAll
expectType<XorAll<[true, true, true]>>(false);
expectType<XorAll<[false, true, true]>>(true);
expectType<XorAll<[false, false, true]>>(true);
expectType<XorAll<[false, false, false]>>(false);
expectType<XorAll<[true, false, false]>>(true);
expectType<XorAll<[true, true, false]>>(true);

expectType<XorAll<[true, true, true], {strict: 1}>>(false);
expectType<XorAll<[false, true, true], {strict: 1}>>(false);

Check failure on line 32 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / TypeScript ~5.8.0

Argument of type 'false' is not assignable to parameter of type 'true'.

Check failure on line 32 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / TypeScript latest

Argument of type 'false' is not assignable to parameter of type 'true'.

Check failure on line 32 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / Node.js 24

Argument of type 'false' is not assignable to parameter of type 'true'.

Check failure on line 32 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / Node.js 20

Argument of type 'false' is not assignable to parameter of type 'true'.

Check failure on line 32 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / Node.js 22

Argument of type 'false' is not assignable to parameter of type 'true'.
expectType<XorAll<[false, false, true], {strict: 1}>>(true);
expectType<XorAll<[false, false, false], {strict: 1}>>(false);
expectType<XorAll<[true, false, false], {strict: 1}>>(true);
expectType<XorAll<[true, true, false], {strict: 1}>>(false);

Check failure on line 36 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / TypeScript ~5.8.0

Argument of type 'false' is not assignable to parameter of type 'true'.

Check failure on line 36 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / TypeScript latest

Argument of type 'false' is not assignable to parameter of type 'true'.

Check failure on line 36 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / Node.js 24

Argument of type 'false' is not assignable to parameter of type 'true'.

Check failure on line 36 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / Node.js 20

Argument of type 'false' is not assignable to parameter of type 'true'.

Check failure on line 36 in test-d/xor.ts

View workflow job for this annotation

GitHub Actions / Node.js 22

Argument of type 'false' is not assignable to parameter of type 'true'.

// @ts-expect-error
expectType<Xor>(never);
expectType<XorAll<[]>>(never);
expectType<Xor<never, any>>(never);
expectType<Xor<any, any>>(never);

// Single value
expectType<XorAll<[true]>>(false);
expectType<XorAll<[false]>>(false);
expectType<XorAll<[boolean]>>(never);
expectType<XorAll<[never]>>(never);
expectType<XorAll<[any]>>(never);

// Test if boolean is position dependent
expectType<XorAll<[boolean, true, true, true]>>(never);
expectType<XorAll<[true, boolean, true, true]>>(never);
expectType<XorAll<[true, true, boolean, true]>>(never);
expectType<XorAll<[true, true, true, boolean]>>(never);

expectType<XorAll<[boolean, false, false, false]>>(never);
expectType<XorAll<[false, boolean, false, false]>>(never);
expectType<XorAll<[false, false, boolean, false]>>(never);
expectType<XorAll<[false, false, false, boolean]>>(never);
Loading