Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
17 changes: 17 additions & 0 deletions source/is-equal.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {IsNever} from './is-never.d.ts';

/**
Returns a boolean for whether the two given types are equal.

Expand Down Expand Up @@ -25,7 +27,22 @@ type Includes<Value extends readonly any[], Item> =
@category Utilities
*/
export type IsEqual<A, B> =
_NonNeverId<A> extends [infer HeadA, ...infer TailA]
? _NonNeverId<B> extends [infer HeadB, ...infer TailB]
? IsEqual<HeadA, HeadB> extends true
? IsEqual<TailA, TailB>
: false
: _IsEqual<A, B>
: _IsEqual<A, B>;

// This version fails the `equalWrappedTupleIntersectionToBeNeverAndNeverExpanded` test in `test-d/is-equal.ts`.
type _IsEqual<A, B> =
(<G>() => G extends A & G | G ? 1 : 2) extends
(<G>() => G extends B & G | G ? 1 : 2)
? true
: false;

type _NonNeverId<T> =
true extends IsNever<T>
? 0
: T;
53 changes: 53 additions & 0 deletions test-d/is-equal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,41 @@ expectType<false>(notEqualAnyAndNever);
const notEqualArrayOfAnyAndArrayOfNever: IsEqual<[any], [never]> = false;
expectType<false>(notEqualArrayOfAnyAndArrayOfNever);

const equalNeverAndNever: IsEqual<never, never> = true;
expectType<true>(equalNeverAndNever);

const equalEmptyArrayAndEmptyArray: IsEqual<[], []> = true;
expectType<true>(equalEmptyArrayAndEmptyArray);

const equalReadonlyEmptyArrayAndReadonlyEmptyArray: IsEqual<readonly [], readonly []> = true;
expectType<true>(equalReadonlyEmptyArrayAndReadonlyEmptyArray);

const notEqualReadonlyEmptyArrayAndReadonlyEmptyArray: IsEqual<readonly [], []> = false;
expectType<false>(notEqualReadonlyEmptyArrayAndReadonlyEmptyArray);

const equalArrayNumberAndArrayNumber: IsEqual<number[], number[]> = true;
expectType<true>(equalArrayNumberAndArrayNumber);

const equalReadonlyArrayNumberAndReadonlyArrayNumber: IsEqual<readonly number[], readonly number[]> = true;
expectType<true>(equalReadonlyArrayNumberAndReadonlyArrayNumber);

const notEqualReadonlyArrayNumberAndReadonlyArrayNumber: IsEqual<readonly number[], number[]> = false;
expectType<false>(notEqualReadonlyArrayNumberAndReadonlyArrayNumber);

type LongTupleNumber = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50];
const equalLongTupleNumberAndLongTupleNumber: IsEqual<LongTupleNumber, LongTupleNumber> = true;
expectType<true>(equalLongTupleNumberAndLongTupleNumber);

type ReadonlyLongTupleNumber = readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50];
const equalLongReadonlyTupleNumberAndLongReadonlyTupleNumber: IsEqual<ReadonlyLongTupleNumber, ReadonlyLongTupleNumber> = true;
expectType<true>(equalLongReadonlyTupleNumberAndLongReadonlyTupleNumber);

const notEqualLongTupleNumberAndLongTupleNumber: IsEqual<readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]> = false;
expectType<false>(notEqualLongTupleNumberAndLongTupleNumber);

const notEqualTupleUnionAndTuple: IsEqual<[0, 1] | [0, 2], [0, 2]> = false;
expectType<false>(notEqualTupleUnionAndTuple);

// Missing all generic parameters.
// @ts-expect-error
type A = IsEqual;
Expand All @@ -33,3 +68,21 @@ expectType<UnionType>(true);

type IntersectionType = IsEqual<{a: 1} | {a: 1}, {a: 1}>; // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents
expectType<IntersectionType>(true);

// Test for PR https://github.com/sindresorhus/type-fest/pull/1231
type BranchOnWrappedTupleMatches<Tpl> = (Tpl extends [[0, 2]] ? ['A', true] : ['A', false]);
type BranchOnWrappedTupleDoesNotMatch<Tpl> = (Tpl extends [[0, 1]] ? ['A', true] : ['A', false]);
type BranchOnTupleMatches<Tpl> = (Tpl extends [0, 2] ? ['A', true] : ['A', false]);
type BranchOnTupleDoesNotMatch<Tpl> = (Tpl extends [0, 1] ? ['A', true] : ['A', false]);

const equalWrappedTupleIntersectionToBeNeverAndNever: IsEqual<(BranchOnWrappedTupleMatches<[[0, 2]]> & BranchOnWrappedTupleDoesNotMatch<[[0, 2]]>), never> = true;
expectType<true>(equalWrappedTupleIntersectionToBeNeverAndNever);

const equalWrappedTupleIntersectionToBeNeverAndNeverExpanded: [0, 2] extends infer Tpl ? IsEqual<(BranchOnWrappedTupleMatches<[Tpl]> & BranchOnWrappedTupleDoesNotMatch<[Tpl]>), never> : never = true;
expectType<true>(equalWrappedTupleIntersectionToBeNeverAndNeverExpanded);

const equalTupleIntersectionToBeNeverAndNever: IsEqual<(BranchOnTupleMatches<[0, 2]> & BranchOnTupleDoesNotMatch<[0, 2]>), never> = true;
expectType<true>(equalTupleIntersectionToBeNeverAndNever);

const equalTupleIntersectionToBeNeverAndNeverExpanded: [0, 2] extends infer Tpl ? IsEqual<(BranchOnTupleMatches<Tpl> & BranchOnTupleDoesNotMatch<Tpl>), never> : never = true;
expectType<true>(equalTupleIntersectionToBeNeverAndNeverExpanded);