Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dabe872
fix: `IsEqual`, `{a: t, b: s}` and `{a: t} & {b: s}` are equal
taiyakihitotsu Jan 27, 2026
74b0e71
Fix: `IsEqual`, returns `true` for nested intersection of objects
taiyakihitotsu Jan 27, 2026
4c9ef58
Merge branch 'main' into fix/is-equal-20260127
taiyakihitotsu Jan 27, 2026
b887c8c
refactor: remoe unused imports
taiyakihitotsu Jan 27, 2026
df808ce
refactor: remove unused import
taiyakihitotsu Jan 27, 2026
b213bb7
revert: `test-d/is-equal.ts`
taiyakihitotsu Jan 27, 2026
4d8773c
fix: define `UniqueUnionDeep` and `UniqueUnion`, to fix `IsEqual` ret…
taiyakihitotsu Jan 27, 2026
fb14a98
doc: update `UniqueUnionDeep` comment
taiyakihitotsu Jan 27, 2026
7f0162f
doc: update `test-d/is-equal.ts` comment
taiyakihitotsu Jan 27, 2026
ba7ccab
test: add tests
taiyakihitotsu Jan 28, 2026
2cc3a26
update: add `UniqueUnionDeep` to define `IsEqual` to strictly get `A|…
taiyakihitotsu Jan 28, 2026
590587c
fix: rename `ExcludeExactly` from `UniqueExclude`, update for a case …
taiyakihitotsu Feb 1, 2026
9af5c71
Merge branch 'main' into fix/is-equal-20260127
taiyakihitotsu Feb 1, 2026
2f1d991
build: trigger CI
taiyakihitotsu Feb 3, 2026
d02b353
revert `IsEqual` and `Paths` changes
som-sm Feb 4, 2026
c6d4206
revert: re-revert d02b353, `source/is-equal.d.ts` and `test-d/is-equa…
taiyakihitotsu Feb 4, 2026
687e6ed
add: `LastOfUnion`, return a type of an union-type (order is not guar…
taiyakihitotsu Feb 5, 2026
631a49f
refactor: `UnionToTuple`, import `LastOfUnion` instead of define
taiyakihitotsu Feb 5, 2026
755df63
Add `ExcludeExactly`, distinguish between different modifiers.
taiyakihitotsu Feb 5, 2026
1f76fd5
fix: `UnionToTuple`, use `ExcludeExactly`, improve performance.
taiyakihitotsu Feb 5, 2026
7d990be
Refactor: `UniqueUnion`, use `UnionToTuple`, improve performance.
taiyakihitotsu Feb 5, 2026
521c373
Merge branch 'main' into fix/is-equal-20260127
taiyakihitotsu Feb 5, 2026
944a74b
temp: ignore arrow-doc.
taiyakihitotsu Feb 5, 2026
a8c2695
refactor: `IsEqual`, remove redundant `SimplifyDeep`, fix the doc.
taiyakihitotsu Feb 5, 2026
390e127
Merge branch 'main' into fix/is-equal-20260127
taiyakihitotsu Feb 7, 2026
18eb4af
Revert "temp: ignore arrow-doc."
taiyakihitotsu Feb 7, 2026
4282283
Update readme.md
sindresorhus Feb 10, 2026
c5ade48
update: `UniqueUnionDeep`, add `IsEqual`'s lambda test cases
taiyakihitotsu Feb 10, 2026
5ad2c4d
update: `UniqueUnionDeep` handles records in tuples.
taiyakihitotsu Feb 12, 2026
9d6aa80
test: add deep identical cases for `IsEqual` and `UniqueUnionDeep`
taiyakihitotsu Feb 12, 2026
946b90f
Merge branch 'main' into fix/is-equal-20260127
taiyakihitotsu Mar 20, 2026
6497043
fix: revert `IsEqual` and the tests. fix `ExcludeExactly` to improve …
taiyakihitotsu Mar 20, 2026
8a7056b
refactor: rename `SimplifyUniqueUnionDeep` to `InternalUniqueUnionDeep`
taiyakihitotsu Mar 21, 2026
4f490d2
fix: Overload cases
taiyakihitotsu Mar 22, 2026
eb8828b
fix: Branded Type with Tuple
taiyakihitotsu Mar 22, 2026
0298dad
update: handles overload cases
taiyakihitotsu Jun 3, 2026
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
116 changes: 92 additions & 24 deletions source/exclude-exactly.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,75 @@
import type {IsNever} from './is-never.d.ts';
import type {IsAny} from './is-any.d.ts';
import type {If} from './if.d.ts';
import type {IsEqual} from './is-equal.d.ts';
import type {IfNotAnyOrNever} from './internal/type.d.ts';
import type {UnionMember} from './union-member.d.ts';

/**
Returns `never` if the 1st and 2nd arguments are identical.
Returns the 1st argument if they are not.
(Note: There are limitations regarding union/intersection types. See `MatchOrNever` or `_IsEqual` in the `source/is-equal.d.ts` documentation.)

@example
```
type A = MatchOrNever<string | number, string>;
//=> string | number

type B = MatchOrNever<string | number, string | number>;
//=> never

type C = MatchOrNever<string | number, unknown>;
//=> string | number

type D = MatchOrNever<string, string | number>;
//=> string
```

This does NOT depend on assignability.

@example
```
type RO_0 = MatchOrNever<{readonly a: 0}, {a: 0}>;
//=> {readonly a: 0}

type RO_1 = MatchOrNever<{a: 0}, {readonly a: 0}>;
//=> {a: 0}
```

Special cases for `unknown` and `never`, which can easily break equality in type-level codebases.

@example
```
type E = MatchOrNever<unknown, never>;
//=> unknown

type F = MatchOrNever<unknown, unknown>;
//=> never

type G = MatchOrNever<never, never>;
//=> never

type H = MatchOrNever<never, unknown>;
//=> never
```

Note that this does not recursively treat identical union/intersection types (e.g., `T | T` or `T & T`) as `T`.
For instance, `{a: 0} | {a: 0}` is considered identical to `{a: 0}`, but nested properties are not normalized.

@example
```
type IDUnion = MatchOrNever<{a: {b: 0}} | {a: {b: 0}}, {a: {b: 0}}>;
//=> never

type RecurivelyIDUnion = MatchOrNever<{a: {b: 0} | {b: 0}}, {a: {b: 0}}>;
//=> {a: {b: 0} | {b: 0}}
```
*/
type MatchOrNever<A, B> =
[unknown, B] extends [A, never]
? A
// This equality code base below doesn't work if `A` is `unknown` and `B` is `never` case.
// So this branch should be wrapped to take care of this.
: (<G>() => G extends A & G | G ? 1 : 2) extends (<G>() => G extends B & G | G ? 1 : 2)
? never
: A;

/**
A stricter version of `Exclude<T, U>` that excludes types only when they are exactly identical.
Expand Down Expand Up @@ -32,26 +99,27 @@ type TestExcludeExactly3 = ExcludeExactly<{a: string} | {a: string; b: string},

@category Improved Built-in
*/
export type ExcludeExactly<Union, Delete> =
IfNotAnyOrNever<
Union,
_ExcludeExactly<Union, Delete>,
// If `Union` is `any`, then if `Delete` is `any`, return `never`, else return `Union`.
If<IsAny<Delete>, never, Union>,
// If `Union` is `never`, then if `Delete` is `never`, return `never`, else return `Union`.
If<IsNever<Delete>, never, Union>
>;

type _ExcludeExactly<Union, Delete> =
IfNotAnyOrNever<Delete,
Union extends unknown // For distributing `Union`
? [Delete extends unknown // For distributing `Delete`
? If<IsEqual<Union, Delete>, true, never>
: never] extends [never] ? Union : never
: never,
// If `Delete` is `any` or `never`, then return `Union`,
// because `Union` cannot be `any` or `never` here.
Union, Union
>;
export type ExcludeExactly<UnionU, DeleteT> =
IsAny<DeleteT> extends true
? IsAny<UnionU> extends true
? never
: UnionU
: IsNever<DeleteT> extends true
? IsNever<UnionU> extends true
? never
: UnionU
: InternalExcludeExactly<UnionU, DeleteT>;

type InternalExcludeExactly<UnionU, DeleteT> =
UnionMember<DeleteT> extends infer D
? true extends IsNever<D>
? UnionU
: InternalExcludeExactly<_ExcludeExactly<UnionU, D>, _ExcludeExactly<DeleteT, D>>
: never;

type _ExcludeExactly<UnionU, DeleteT> =
UnionU extends unknown // Only for union distribution.
? MatchOrNever<UnionU, DeleteT>
: never;

export {};
146 changes: 145 additions & 1 deletion source/internal/type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 {UnknownArray} from '../unknown-array.d.ts';
import type {UnionToIntersection} from '../union-to-intersection.d.ts';
import type {UnionToTuple} from '../union-to-tuple.d.ts';

/**
Matches any primitive, `void`, `Date`, or `RegExp` value.
Expand Down Expand Up @@ -36,6 +36,17 @@ Needed to handle the case of a single call signature with properties.

Multiple call signatures cannot currently be supported due to a TypeScript limitation.
@see https://github.com/microsoft/TypeScript/issues/29732

NOTE:
Deduplicate identical union/intersection types in overloads beforehand with `UniqueOverload` if you require a strict result.

@example
```
type A = HasMultipleCallSignatures<UniqueOverload<{(a: number): 0; (a: string): 1; (a: string): 1}>>;
//=> true
type B = HasMultipleCallSignatures<{(a: number): 0; (a: string): 1; (a: string): 1}>;
//=> false
```
*/
export type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknown> =
T extends {(...arguments_: infer A): unknown; (...arguments_: infer B): unknown}
Expand Down Expand Up @@ -162,4 +173,137 @@ export type IsExactOptionalPropertyTypesEnabled = [(string | undefined)?] extend
? false
: true;

/**
In TypeScript, `{a: T}` and `{a: T} | {a: T}` are assignable mutually but automatically simplified.
And it disturbs a calculation of `IsEqual`.

@example
```
type NT = _IsEqual<{z: {a: 0}}, {z: {a: 0} | {a: 0}}>; // => false
```

`UniqueUnionDeep` is a helper type function: removes a duplicated type and keeps the other types recursively.
But union distribution also works as usual outside of objects.

@example
```
type UniqueUnionDeepTest = SimplifyDeep<UniqueUnionDeep<{z: {a: {aa: 0} | {aa: 0}} | {a: {aa: 0} | {aa: 0}} | {b: 0}; x: '1'}>>;
//=> {z: {a: {aa: 0}} | {b: 0}; x: '1'}

type UniqueUnionDeepKeepDistributionTest = SimplifyDeep<UniqueUnionDeep<{z: {a: 0} | {a: 0}; x: '1'} | {z: {a: 0} | {a: 0}; x: '2'}>>;
//=> {z: {a: 0}; x: '1'} | {z: {a: 0}; x: '2'}

type UniqueUnionDeepArguments = SimplifyDeep<UniqueUnionDeep<(a: {a: number} | {a: number}) => {b: number} | {b: number}>>;
//=> (a: {a: number} | {a: number}) => {b: number} | {b: number}

type UniqueUnionDeepArgumentsDeep = SimplifyDeep<UniqueUnionDeep<(a: {a: number} | {a: number}) => {b: {b: number} | {b: number}}>>;
//=> (a: {a: number}) => {b: {b: number}}
```
*/
export type UniqueUnionDeep<U> =
/** Note: Wrapping this with `SimplifyDeep`, `test-d/is-equal.ts` fails in `Branded Type with Tuple`. */
RecurseUniqueUnionDeep<{r: U}>['r'];

/**
Note: Wrapping this with `Simplify`, `test-d/exact.ts` fails in "Spec: recursive type with union".
*/
type InternalUniqueUnionDeep<U extends object> =
{[K in keyof U]: UniqueUnion<RecurseUniqueUnionDeep<U[K]>>};

type InternalUniqueOverloadDeep<U extends (...args_: any[]) => unknown> =
/** `Parametes` and `ReturnType` results are possible to be object or lambda; both should be passed into `UniqueUnionDeep`. */
HasMultipleCallSignatures<UniqueOverload<U>> extends true
? U
: (...args: UniqueUnionDeep<Parameters<U>> extends infer A extends any[] ? A : never) => (UniqueUnionDeep<ReturnType<U>>);

type RecurseUniqueUnionDeep<U> =
U extends Record<PropertyKey, unknown>
? InternalUniqueUnionDeep<U>
: U extends UnknownArray
? InternalUniqueUnionDeep<U>
: U extends Lambda
/** If `IsNever<keyof U>` returns `true`, `U` is composed only of overloads. */
? IsNever<keyof U> extends true
? InternalUniqueOverloadDeep<U>
/** Separates an object into the record and the overload parts. */
: UniqueUnionDeep<Pick<U, keyof U>> & UniqueUnionDeep<UniqueOverload<U>>
: U;

type UniqueOverload<T extends (...args_: any[]) => unknown> =
T extends {
(...args_: infer Arguments0): infer Return0;
(...args_: infer Arguments1): infer Return1;
(...args_: infer Arguments2): infer Return2;
(...args_: infer Arguments3): infer Return3;
}
? DedupeTuple<[(...args_: Arguments0) => Return0, (...args_: Arguments1) => Return1, (...args_: Arguments2) => Return2, (...args_: Arguments3) => Return3]> extends infer TupleOverload extends Lambda[]
? TupleToOverload<TupleOverload>
: never
: never; // Unreacheable.

type TupleToOverload<T extends Lambda[]> = _TupleToOverload<T>;

type _TupleToOverload<T extends Lambda[]> =
T extends [(...args_: infer Arguments0) => infer Return0, (...args_: infer Arguments1) => infer Return1, (...args_: infer Arguments2) => infer Return2, (...args_: infer Arguments3) => infer Return3]
? {(...args_: Arguments0): Return0; (...args_: Arguments1): Return1; (...args_: Arguments2): Return2; (...args_: Arguments3): Return3}
: T extends [(...args_: infer Arguments0) => infer Return0, (...args_: infer Arguments1) => infer Return1, (...args_: infer Arguments2) => infer Return2]
? {(...args_: Arguments0): Return0; (...args_: Arguments1): Return1; (...args_: Arguments2): Return2}
: T extends [(...args_: infer Arguments0) => infer Return0, (...args_: infer Arguments1) => infer Return1]
? {(...args_: Arguments0): Return0; (...args_: Arguments1): Return1}
: T extends [(...args_: infer Arguments0) => infer Return0]
? (...args_: Arguments0) => Return0
: never;

/**
@example
```
type DedupeTuple_Test0 = DedupeTuple<[]>;
//=> []
type DedupeTuple_Test1 = DedupeTuple<[0, 1]>;
//=> [0, 1]
type DedupeTuple_Test2 = DedupeTuple<[0, 1, number]>;
//=> [0, 1, number]
type DedupeTuple_Test3 = DedupeTuple<[number, 0, 1, number]>;
//=> [number, 0, 1]
type DedupeTuple_Test4 = DedupeTuple<[never, string, '9']>;
//=> [never, string, '9']
type DedupeTuple_Test5 = DedupeTuple<[string, never, string, '9']>;
//=> [string, never, '9']
type DedupeTuple_Test6 = DedupeTuple<[unknown, string, '9']>;
//=> [unknown, string, '9']
type DedupeTuple_Test7 = DedupeTuple<[string, unknown, string, '9']>;
//=> [string, unknown, '9']
```
*/
export type DedupeTuple<Tuple extends readonly unknown[]> = _DedupeTuple<Tuple>;

type _DedupeTuple<Tuple extends readonly unknown[], Return extends unknown[] = [], Compare = never> =
Tuple['length'] extends 0
? Return
: Tuple extends [infer Head, ...infer Tail]
? IsNever<Compare> extends true
? _DedupeTuple<Tail, [...Return, Head], {_compare: Head}>
: [Compare extends unknown // Union Distribution.
? _IsEqual<{_compare: Head}, Compare>
: never] extends [false]
? _DedupeTuple<Tail, [...Return, Head], Compare | {_compare: Head}>
: _DedupeTuple<Tail, Return, Compare>
: never; // Unreacheable.

type _IsEqual<A, B> =
(<G>() => G extends A & G | G ? 1 : 2) extends
(<G>() => G extends B & G | G ? 1 : 2)
? true
: false;

type Lambda = ((...args: any[]) => any);

/**
The flat version of `UniqueUnionDeep`.
*/
export type UniqueUnion<U> =
UnionToTuple<U> extends infer E extends readonly unknown[] // Improve performance.
? E[number]
: never; // Unreachable.

export {};
23 changes: 18 additions & 5 deletions source/is-equal.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {UniqueUnionDeep} from './internal/type.d.ts';

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

Expand Down Expand Up @@ -25,13 +27,24 @@ type Includes<Value extends readonly any[], Item> =
@category Utilities
*/
export type IsEqual<A, B> =
[A] extends [B]
? [B] extends [A]
? _IsEqual<A, B>
: false
[A, B] extends [B, A]
? _IsEqual<UniqueUnionDeep<A>, UniqueUnionDeep<B>>
: false;

// This version fails the `equalWrappedTupleIntersectionToBeNeverAndNeverExpanded` test in `test-d/is-equal.ts`.
/**
Note that this doesn't regard the identical union/intersection type `T | T` and/or `T & T` as `T` recursively.
e.g., `{a: 0} | {a: 0}` and/or `{a: 0} & {a: 0}` as `{a: 0}`.

@example
```
type IDUnionIsTrue = _IsEqual<{a: {b: 0}} | {a: {b: 0}}, {a: {b: 0}}>;
//=> true
type RecurivelyIDUnionIsFalse = _IsEqual<{a: {b: 0} | {b: 0}}, {a: {b: 0}}>;
//=> false
```

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)
Expand Down
38 changes: 38 additions & 0 deletions test-d/internal/unique-union-deep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {expectType} from 'tsd';
import type {UniqueUnionDeep} from '../../source/internal/type.d.ts';

// The returns of `UniqueUnionDeep` are expected to be equal to `UniqueUnion`, if there's no recursive object type in the passed union type.
// That's why those tests are pasted from 'test-d/internal/union-unique.ts'.

expectType<never>({} as UniqueUnionDeep<never>);
expectType<string>({} as UniqueUnionDeep<string>);
expectType<{a: 0}>({} as UniqueUnionDeep<{a: 0}>);
expectType<unknown>({} as UniqueUnionDeep<unknown>);
expectType<any>({} as UniqueUnionDeep<any>);
expectType<[unknown]>({} as UniqueUnionDeep<[unknown]>);
expectType<[any]>({} as UniqueUnionDeep<[any]>);

expectType<{a: 0} | {a: 1}>({} as UniqueUnionDeep<{a: 0} | {a: 0} | {a: 0} | {a: 1}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// `{a: t}` isn't excluded by `{a: T}` even if `T` includes `t`.
expectType<{a: 0} | {a: 1} | {a: number}>({} as UniqueUnionDeep<{a: 0} | {a: 0} | {a: 0} | {a: 1} | {a: number}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// `readonly`, `optional`, both, and general object key shouldn't be mutually equal.
expectType<{a: number} | {readonly a: number} | {a?: number} | {readonly a?: number}>({} as UniqueUnionDeep<{a: number} | {readonly a: number} | {a?: number} | {readonly a?: number} | {a: number} | {readonly a: number} | {a?: number} | {readonly a?: number}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// Empty tuple isn't removed by `T[]`.
expectType<[0, 1, 2] | [0, 1] | [] | number[]>({} as UniqueUnionDeep<[0, 1, 2] | [0, 1, 2] | [0, 1, 2] | [0, 1] | [] | number[]>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// `T[]` doesn't delete tuples of `[t, ...t]` even if `T` includes `t`.
expectType<[0, 1, 2] | ['0', unknown] | [0, unknown]>({} as UniqueUnionDeep<[0, 1, 2] | [0, 1, 2] | [0, 1, 2] | [0, unknown] | ['0', unknown]>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// `[u, ...u]` doesn't delete tuple of `[v, ...y]` even if `u` includes `v`.
expectType<[0, 1, 2] | ['0', unknown] | [0, unknown] | number[]>({} as UniqueUnionDeep<[0, 1, 2] | [0, 1, 2] | [0, 1, 2] | [0, unknown] | ['0', unknown] | number[]>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

expectType<{z: {a: {aa: 0}} | {b: 0}; x: '1'}>({} as UniqueUnionDeep<{z: {a: {aa: 0} | {aa: 0}} | {a: {aa: 0} | {aa: 0}} | {b: 0}; x: '1'}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents
expectType<{z: {a: 0}; x: '1'} | {z: {a: 0}; x: '2'}>({} as UniqueUnionDeep<{z: {a: 0} | {a: 0}; x: '1'} | {z: {a: 0} | {a: 0}; x: '2'}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

type UniqueUnionDeepArgumentsDeep = UniqueUnionDeep<(a: {a: number} | {a: number}) => {b: {b: number} | {b: number}}>; // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents
expectType<(a: {a: number}) => {b: {b: number}}>({} as UniqueUnionDeepArgumentsDeep);

expectType<{a: [{b: 0}]}>({} as UniqueUnionDeep<{a: [{b: 0} | {b: 0}]} | {a: [{b: 0}]}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents
27 changes: 27 additions & 0 deletions test-d/internal/unique-union.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {expectType} from 'tsd';
import type {UniqueUnion} from '../../source/internal/type.d.ts';

expectType<never>({} as UniqueUnion<never>);
expectType<string>({} as UniqueUnion<string>);
expectType<{a: 0}>({} as UniqueUnion<{a: 0}>);
expectType<unknown>({} as UniqueUnion<unknown>);
expectType<any>({} as UniqueUnion<any>);
expectType<[unknown]>({} as UniqueUnion<[unknown]>);
expectType<[any]>({} as UniqueUnion<[any]>);

expectType<{a: 0} | {a: 1}>({} as UniqueUnion<{a: 0} | {a: 0} | {a: 0} | {a: 1}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// `{a: t}` isn't excluded by `{a: T}` even if `T` includes `t`.
expectType<{a: 0} | {a: 1} | {a: number}>({} as UniqueUnion<{a: 0} | {a: 0} | {a: 0} | {a: 1} | {a: number}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// `readonly`, `optional`, both, and general object key shouldn't be mutually equal.
expectType<{a: number} | {readonly a: number} | {a?: number} | {readonly a?: number}>({} as UniqueUnion<{a: number} | {readonly a: number} | {a?: number} | {readonly a?: number} | {a: number} | {readonly a: number} | {a?: number} | {readonly a?: number}>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// Empty tuple isn't removed by `T[]`.
expectType<[0, 1, 2] | [0, 1] | [] | number[]>({} as UniqueUnion<[0, 1, 2] | [0, 1, 2] | [0, 1, 2] | [0, 1] | [] | number[]>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// `T[]` doesn't delete tuples of `[t, ...t]` even if `T` includes `t`.
expectType<[0, 1, 2] | ['0', unknown] | [0, unknown]>({} as UniqueUnion<[0, 1, 2] | [0, 1, 2] | [0, 1, 2] | [0, unknown] | ['0', unknown]>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents

// `[u, ...u]` doesn't delete tuples of `[v, ...y]` even if `u` includes `v`.
expectType<[0, 1, 2] | ['0', unknown] | [0, unknown] | number[]>({} as UniqueUnion<[0, 1, 2] | [0, 1, 2] | [0, 1, 2] | [0, unknown] | ['0', unknown] | number[]>); // eslint-disable-line @typescript-eslint/no-duplicate-type-constituents
Loading