diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index 85dac83ff..f6b2c149c 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -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. @@ -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` => `false | true` => `boolean`. + */ +export type Extends = IsNever extends true ? IsNever : [Left] extends [Right] ? true : false; + /** Returns a boolean for whether the two given types extends the base type. */ @@ -40,9 +48,19 @@ export type HasMultipleCallSignatures 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 [false] ? false : true; +export type IsNotFalse = Not>; + +/** +Returns a boolean for whether the given `boolean` Union members are all `true`. +*/ +export type IsTrue = Extends; + +/** +Returns a boolean for whether the given `boolean` Union members are all `false`. +*/ +export type IsFalse = Extends; /** Returns a boolean for whether the given type is primitive value or primitive type. @@ -59,7 +77,7 @@ IsPrimitive //=> false ``` */ -export type IsPrimitive = [T] extends [Primitive] ? true : false; +export type IsPrimitive = Extends; /** Returns a boolean for whether A is false. @@ -99,3 +117,8 @@ type C = IfNotAnyOrNever; */ export type IfNotAnyOrNever = If, IfAny, If, IfNever, IfNotAnyOrNever>>; + +/** +Determines if a type is either `never` or `any`. +*/ +export type IsAnyOrNever = Or, IsNever>; diff --git a/source/xor.d.ts b/source/xor.d.ts new file mode 100644 index 000000000..ddfb65a52 --- /dev/null +++ b/source/xor.d.ts @@ -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 = XorAll<[A, B]>; + +type XorAll = ( + ApplyDefaultOptions extends infer ResolvedOptions extends Required + ? IsAnyOrNever extends false + ? Includes extends false + ? [IsFalse | IsTrue] extends [false] + ? ResolvedOptions['strict'] extends infer TrueCount extends number + ? TrueCount extends -1 ? true + : IsEqual, TrueCount> + : never + : false + : never + : never + : never +); diff --git a/test-d/xor.ts b/test-d/xor.ts new file mode 100644 index 000000000..b4787c229 --- /dev/null +++ b/test-d/xor.ts @@ -0,0 +1,60 @@ +import {expectType} from 'tsd'; +import type {Xor, XorAll} from '../source/xor.d.ts'; + +declare const never: never; + +// Xor +expectType>(false); +expectType>(true); +expectType>(true); +expectType>(false); +expectType>(never); +expectType>(never); +expectType>(never); + +expectType>(never); +expectType>(never); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(never); + +// XorAll +expectType>(false); +expectType>(true); +expectType>(true); +expectType>(false); +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); +expectType>(true); +expectType>(false); +expectType>(true); +expectType>(false); + +// @ts-expect-error +expectType(never); +expectType>(never); +expectType>(never); +expectType>(never); + +// Single value +expectType>(false); +expectType>(false); +expectType>(never); +expectType>(never); +expectType>(never); + +// Test if boolean is position dependent +expectType>(never); +expectType>(never); +expectType>(never); +expectType>(never); + +expectType>(never); +expectType>(never); +expectType>(never); +expectType>(never);