type Helper<T, Copy = T> =
T extends any
? Copy extends T
? true
: false
: never
type IsUnion<T> = Equal<Helper<T>, boolean>
Разберём строку за строкой, начиная с helper-функции
type Helper<T, Copy = T>
T extends any
? дальнейшая логика
: never
Это выражение всегда будет верно, мы никогда не попадём в ветку со значением never, поэтому фактически там может быть всё, что угодно: false, 42, 'foo', не имеет значения. Обозначим её как never, чтобы наглядно это показать и перейдем к дальнейшей логике
T extends any
? Copy extends T
? true
: false
Здесь происходит основной фокус решения. Чтобы понять наглядно, только для этого примера, давайте представим себе union типы в качестве массивов. Например, тип string | number как ['string', 'number'].
Эта конструкция запускает обход этих "массивов", сравнение их значений одного за другим и сохраняет результат в новый union тип ('массив'). Представьте себе это как некий аналог подобного javascript-кода:
const result = []
for (let k of ['string', 'number']) {
for (let n of ['string', 'number']) {
result.push(k === n)
}
Результатом для нашего 'массива' будет [true, false, false, true], а последовательность обхода:
| Шаг № | k | n | result | |--------|--------|--------|--------| | 0 | string | string | [true] | | 1 | string | number | [true, false] | | 2 | number | string | [true, false, false] | | 3 | number | number | [true, false, false, true] |
Чему будет равен union true | false | false | true
?
Правильно - true | false
А чему будет равен юнион true | false
?
Верно - boolean
Идея в том, что для любого union типа при двойном обходе результатом всегда будет boolean,
потому что невозможно сравнить два и более элемента друг с другом и получить только true, это всегда будет boolean (true | false)
- type 1 === type 1 | type1 === type2
А для любого другого типа результатом всегда будет только true - singleType === singleType
Зная это, мы можем сравнить результат helper функции с boolean:
Equal<Helper<T>, boolean>
Helper<T> extends boolean
будет означать что мы имеем дело с union типомtrue extends boolean
- только один типSolution by alex-altay #34845
// your answers
type IsUnion<T, U = T> = [T] extends [never]
? false
: T extends unknown
? [U] extends [T]
? false
: true
: never;
Solution by AndreGeng #34750
type IsUnion<T, C = T> = (T extends T ? C extends T ? true : unknown : never) extends true ? false : true;
Solution by devshinthant #34624
// solution 1
type IsUnion<T, B = T> = [T] extends [never] ? false :
T extends B ? [B] extends [T] ? false : true : never
// solution 2
type IsUnion<T, B = T> =
(T extends T ? B extends T ? true : unknown : never) extends true ? false : true
Solution by Jayce-liang #34338
type IsUnion<T, U = T> = [T] extends [never] ? false : T extends U ? [U] extends [T] ? false : true : never
Solution by ouzexi #34029
// your answers
type IsUnion<T, U = T> =[T] extends [never] ? false :
T extends T ? [T, U] extends [U, T] ? false : true : false
Solution by heyuelan #33897
// 你的答案
type IsUnion<T, U = T> = [T] extends [never] ? false : T extends U ? [U] extends [T] ? false: true : false;
Solution by HelloGGG #33423
// your answers
type IsUnion<T, K = T> = [T] extends [never]
? false
: K extends K
? [T] extends [K]
? false
: true
: never
Solution by pea-sys #32610
type IsUnion<T, C = T> =
(T extends unknown
? C extends T
? true
: false
: never) extends true ? false : true
분배법칙이 일어났을 때의 T와 정적으로 C가 가지고 있는 타입의 관계를 비교하여 Union인지 아닌지를 체크할 수 있다.
Solution by dev-hobin #32422
type IsUnion<T, K = T> = [T] extends [never]
? false
: K extends K
? [T] extends [K]
? false
: true
: never
Solution by yoonnokdoo #32395
type IsUnion<T, S = T> = [T] extends [never]
? false
: S extends S
? Equal<T, S> extends true
? false
: true
: never
type IsUnion<T, S = T> = [T] extends [never]
? false
: S extends S
? NotEqual<T, S>
: never
Solution by Heonys #32100
// your answers
// 使用 296 的 Permutation,得出笛卡尔乘积组合解决
type Permutation<T, U extends T = T> = [T] extends [never]
? []
: U extends U
? [U, ...Permutation<Exclude<T, U>>]
: never;
type IsUnion<T> = Permutation<T>['length'] extends (0 | 1) ? false : true;
Solution by wenxiangdong #31760
type IsUnion<T, U = T> = [T] extends [never]
? false
: T extends infer V
? [Exclude<U, V>] extends [never] ? false : true
: never
Solution by kai-phan #31667
type IsUnion<T> = { [K in T as T extends K ? 'false' : 'true']: true } extends { true: true } ? true : false;
Basic idea: match every member in a union against everything and map this to a mapped type with "true"/"false" keys, then try to match for "true" key.
PS: I'm not sure if I used the correct language or if I properly understood how everything works.
Solution by tany1 #31604
type IsUnion<T, B = T> = [T] extends [never]
? false
: T extends B
? [B] extends [T]
? false
: true
: never;
Solution by vipulpathak113 #31538
With NotEqual
.
// your answers
type IsUnion<T> = NotEqual<[T] & T, (T extends T ? [T] : never) & T>;
Solution by sugoroku-y #31477
Referenced Equal
.
// your answers
type IsUnion<T> = (
T extends T ? <S>() => S extends T ? 1 : 2 : never
) extends <S>() => S extends T ? 1 : 2
? false
: true;
@doox911-opensource commented
I think this is the best solution. And great explanation.
But the test:
Expect<Equal<IsUnion<(() => any)|(() => 15)>, true >>,
failed
This test case has also been successful.
Solution by sugoroku-y #31295
type IsUnionImpl<T, C extends T = T> = (T extends T ? C extends T ? true : unknown : never) extends true ? false : true;
type IsUnion<T> = IsUnionImpl<T>;
Solution by MyeonghoonNam #30992
type IsUnion<T, K = T> =
[T] extends [never]
? false
: K extends K
? [Exclude<T, K>] extends [never]
? false
: true
: false;
Solution by vprokashev #30955
// your answers
type UnionToArray<T> = T extends T ? Array<T> : never
type IsUnion<T> = [T] extends [never] ? false : Array<T> extends UnionToArray<T> ? false : true
Solution by yangdonglai #30722
type IsUnion<T, R = T> = [T] extends [never] ? false : (T extends any ? ([Exclude<R, T>] extends [never] ? false : true) : true);
Solution by kai-phan #30391
export type IsUnion<U> = _IsUnionImpl<U, U>;
/** @internal */
type _IsUnionImpl<U, K extends U> = IsNever<U> extends true
? false
: K extends K
? BoolNot<TypeEq<U, K>>
: never;
type IsNever<T> = [T] extends [never] ? true : false;
// https://github.com/microsoft/TypeScript/issues/27024
type TypeEq<A, B> =
(<T>() => T extends A ? 1 : 2) extends
(<T>() => T extends B ? 1 : 2)
? true
: false;
type BoolNot<A extends boolean> =
TypeEq<A, true> extends true
? false
: TypeEq<A, false> extends true
? true
: never;
/**
* @param _relation `"=" | "<=" | "!="`
* @description
* - `expectType<A, B>("=")` passes if `A` is equal to `B`.
* - `expectType<A, B>("<=")` passes if `A` extends `B`.
* - `expectType<A, B>("!=")` passes if `A` is not equal to `B`.
*/
const expectType = <A, B>(
_relation: TypeEq<A, B> extends true
? '<=' | '='
: TypeExtends<A, B> extends true
? '!=' | '<='
: '!<=' | '!='
): void => undefined;
expectType<IsUnion<never>, false>('=');
expectType<IsUnion<string>, false>('=');
expectType<IsUnion<number | string>, true>('=');
expectType<IsUnion<[number | string]>, false>('=');
expectType<
IsUnion<Readonly<{ a: 0 }> | Readonly<{ a: 1; b: 1 }> | Readonly<{ b: 2 }>>,
true
>('=');
expectType<IsUnion<Record<number, number> | Record<string, number>>, true>('=');
expectType<
IsUnion<
| Readonly<{ a: 0 }>
| Readonly<{ a: 1; b: 1 }>
| Readonly<{ b: 2 }>
| Record<string, number>
>,
true
>('=');
WIP
Solution by noshiro-pf #29976
type IsUnionImpl<T, C extends T = T> =
(T extends infer TItem // Iterate over T, here TItem is an item from the original union T. Ingredients 1&2
? C extends TItem // C holds the original union T. Does union T extends an item from it? // Ingredient 3
? true // yes. that could only be true if union T consist of one item
: false // no
: never) extends true ? false : true // have we got true from the above? yes - it's not a union
type IsUnion<T> = IsUnionImpl<T>
Solution by qianzhong516 #29913
type IsUnion<T, U = T> = [T] extends [never] ? false : T extends U ? IsUnion<Exclude<U, T>, T> : true
Solution by Yirujet #29539
// your answers
type IsUnion<T> = HasMoreThanOnePermutation<T>;
type Permutation<T, acc extends any[] = [], ALL = T> = [T] extends [never]
? acc
: T extends T
? Permutation<Exclude<ALL, T | acc[number]>, [...acc, T], ALL>
: acc;
type HasMoreThanOnePermutation<T extends any> = Permutation<T> extends [
infer U,
...infer R
]
? [U] extends [never] // size 0
? false
: R extends never[] // size 1
? false
: true //more than 1 permutation
: false; // should never happen
Solution by lamine-ndouop-deel #29422
type IsUnion<T, U = T> =[T] extends [never] ? false : T extends U ? [U] extends [T] ? false : true : never
Solution by IvanKoigerov #28884
type IsNever<T> = [T] extends [never] ? true : false;
type IsUnion<T, I = T> = IsNever<T> extends true ? false : (T extends I ? [I] extends [T] ? false : true : false)
Solution by hajeonghun #28759
type IsEqual<A, B> = (
type IsUnion<T, U = T> = [T] extends [never] ? false : T extends U ? IsEqual<T, U> extends true ? false : true : never;
Solution by DoubleWoodLin #28643
// your answers
type IsUnion<T, B = T> = [T] extends [never] ? false : T extends B ? [B] extends [T] ? false : true : never;
Solution by ixiaolong2023 #27786
type IsUnion<T, C = T> = [T] extends [never]
? false
: C extends C
? [Exclude<T, C>] extends [never]
? false
: true
: never;
Solution by idebbarh #27600