Skip to content

Commit

Permalink
SharedUnionFieldsDeep: Skip if input type is not a union type (#798)
Browse files Browse the repository at this point in the history
  • Loading branch information
Emiyaaaaa authored Jan 21, 2024
1 parent eb96609 commit 6f1db93
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 2 deletions.
35 changes: 35 additions & 0 deletions source/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,3 +436,38 @@ type InternalUnionMax<N extends number, T extends UnknownArray = []> =
: T['length'] extends N
? InternalUnionMax<Exclude<N, T['length']>, T>
: InternalUnionMax<N, [...T, unknown]>;

/**
Returns a boolean for whether the given type is a union type.
@example
```
type A = IsUnion<string | number>;
//=> true
type B = IsUnion<string>;
//=> false
```
*/
export type IsUnion<T> = InternalIsUnion<T>;

/**
The actual implementation of `IsUnion`.
*/
type InternalIsUnion<T, U = T> =
(
// @link https://ghaiklor.github.io/type-challenges-solutions/en/medium-isunion.html
IsNever<T> 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
7 changes: 5 additions & 2 deletions source/shared-union-fields-deep.d.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -108,10 +108,13 @@ function displayPetInfo(petInfo: SharedUnionFieldsDeep<Cat | Dog>['info']) {
@category Union
*/
export type SharedUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions = {recurseIntoArrays: false}> =
// If `Union` is not a union type, return `Union` directly.
IsUnion<Union> 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<unknown, unknown> | ReadonlySet<unknown>]
: [Union] extends [NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>]
? Union
: [Union] extends [UnknownArray]
? Options['recurseIntoArrays'] extends true
Expand Down
18 changes: 18 additions & 0 deletions test-d/internal/is-union.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {expectType} from 'tsd';
import type {IsUnion} from '../../source/internal';

expectType<IsUnion<1>>(false);
expectType<IsUnion<true>>(false);
expectType<IsUnion<'foo'>>(false);
expectType<IsUnion<[]>>(false);
expectType<IsUnion<{}>>(false);
expectType<IsUnion<1 & {}>>(false);
expectType<IsUnion<never>>(false);
expectType<IsUnion<unknown>>(false);
expectType<IsUnion<any>>(false);

expectType<IsUnion<1 | 2>>(true);
expectType<IsUnion<'foo' | 'bar'>>(true);
expectType<IsUnion<'foo' | 'bar' | 1>>(true);
expectType<IsUnion<'foo' | 1>>(true);
expectType<IsUnion<[] | {}>>(true);
5 changes: 5 additions & 0 deletions test-d/shared-union-fields-deep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@ expectType<{tuple: [number | boolean, ...Array<string | boolean>]}>(nonFixedLeng

declare const nonFixedLengthTuple2: SharedUnionFieldsDeepRecurseIntoArrays<{tuple: [number, ...string[]]} | {tuple: [number, string, ...boolean[]]}>;
expectType<{tuple: [number, string, ...Array<string | boolean>]}>(nonFixedLengthTuple2);

// Test for same type
type TestingType2 = TestingType & {foo: any};
declare const same: SharedUnionFieldsDeepRecurseIntoArrays<TestingType | TestingType2>;
expectType<TestingType>(same);

0 comments on commit 6f1db93

Please sign in to comment.