diff --git a/source/internal.d.ts b/source/internal.d.ts index e9ea5fa81..abc25a6ee 100644 --- a/source/internal.d.ts +++ b/source/internal.d.ts @@ -436,3 +436,38 @@ type InternalUnionMax = : T['length'] extends N ? InternalUnionMax, T> : InternalUnionMax; + +/** +Returns a boolean for whether the given type is a union type. + +@example +``` +type A = IsUnion; +//=> true + +type B = IsUnion; +//=> false +``` +*/ +export type IsUnion = InternalIsUnion; + +/** +The actual implementation of `IsUnion`. +*/ +type InternalIsUnion = +( + // @link https://ghaiklor.github.io/type-challenges-solutions/en/medium-isunion.html + IsNever extends true + ? false + : T extends any + ? [U] extends [T] + ? false + : true + : never +) extends infer Result + // In some cases `Result` will return `false | true` which is `boolean`, + // that means `T` has at least two types and it's a union type, + // so we will return `true` instead of `boolean`. + ? boolean extends Result ? true + : Result + : never; // Should never happen diff --git a/source/shared-union-fields-deep.d.ts b/source/shared-union-fields-deep.d.ts index f4dbb8b69..61d91f56f 100644 --- a/source/shared-union-fields-deep.d.ts +++ b/source/shared-union-fields-deep.d.ts @@ -1,4 +1,4 @@ -import type {NonRecursiveType, UnionMin, UnionMax, TupleLength, StaticPartOfArray, VariablePartOfArray} from './internal'; +import type {NonRecursiveType, UnionMin, UnionMax, TupleLength, StaticPartOfArray, VariablePartOfArray, IsUnion} from './internal'; import type {IsNever} from './is-never'; import type {UnknownArray} from './unknown-array'; @@ -108,10 +108,13 @@ function displayPetInfo(petInfo: SharedUnionFieldsDeep['info']) { @category Union */ export type SharedUnionFieldsDeep = +// If `Union` is not a union type, return `Union` directly. +IsUnion extends false + ? Union // `Union extends` will convert `Union` // to a [distributive conditionaltype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types). // But this is not what we want, so we need to wrap `Union` with `[]` to prevent it. - [Union] extends [NonRecursiveType | ReadonlyMap | ReadonlySet] + : [Union] extends [NonRecursiveType | ReadonlyMap | ReadonlySet] ? Union : [Union] extends [UnknownArray] ? Options['recurseIntoArrays'] extends true diff --git a/test-d/internal/is-union.ts b/test-d/internal/is-union.ts new file mode 100644 index 000000000..be28a5401 --- /dev/null +++ b/test-d/internal/is-union.ts @@ -0,0 +1,18 @@ +import {expectType} from 'tsd'; +import type {IsUnion} from '../../source/internal'; + +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); + +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); diff --git a/test-d/shared-union-fields-deep.ts b/test-d/shared-union-fields-deep.ts index 005f2f409..ab34bcd21 100644 --- a/test-d/shared-union-fields-deep.ts +++ b/test-d/shared-union-fields-deep.ts @@ -123,3 +123,8 @@ expectType<{tuple: [number | boolean, ...Array]}>(nonFixedLeng declare const nonFixedLengthTuple2: SharedUnionFieldsDeepRecurseIntoArrays<{tuple: [number, ...string[]]} | {tuple: [number, string, ...boolean[]]}>; expectType<{tuple: [number, string, ...Array]}>(nonFixedLengthTuple2); + +// Test for same type +type TestingType2 = TestingType & {foo: any}; +declare const same: SharedUnionFieldsDeepRecurseIntoArrays; +expectType(same);