01097-medium-isunion

Back

Решение

type Helper<T, Copy = T> = 
  T extends any 
    ? Copy extends T
      ? true
      : false
    : never

type IsUnion<T> = Equal<Helper<T>, boolean>

Как это работает

Разберём строку за строкой, начиная с helper-функции

1. Создадим копию проверяемого типа для использования в двух вложенных циклах:

type Helper<T, Copy = T>

2. Запустим первый цикл проверяемого типа

T extends any
  ? дальнейшая логика
  : never

Это выражение всегда будет верно, мы никогда не попадём в ветку со значением never, поэтому фактически там может быть всё, что угодно: false, 42, 'foo', не имеет значения. Обозначим её как never, чтобы наглядно это показать и перейдем к дальнейшей логике

3. Второй цикл и сравнение

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] |

4. Вернемся от нашей аналогии с массивом к настоящему результату - union типу

Чему будет равен 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

5. Финал

Зная это, мы можем сравнить результат helper функции с boolean: Equal<Helper<T>, 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.

Playground

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

Implementation

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;

Test

/**
 * @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
>('=');

Description

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

image

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> = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false;

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