Skip to content

Commit

Permalink
Add UnionToIntersection type (#89)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
ifiokjr and sindresorhus committed Mar 9, 2020
1 parent 8ce2a33 commit bed43c1
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 2 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Expand Up @@ -20,6 +20,7 @@ export {AsyncReturnType} from './source/async-return-type';
export {ConditionalExcept} from './source/conditional-except';
export {ConditionalKeys} from './source/conditional-keys';
export {ConditionalPick} from './source/conditional-pick';
export {UnionToIntersection} from './source/union-to-intersection';

// Miscellaneous
export {PackageJson} from './source/package-json';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Expand Up @@ -79,6 +79,7 @@ Click the type names for complete docs.
- [`ConditionalKeys`](source/conditional-keys.d.ts) - Extract keys from a shape where values extend the given `Condition` type.
- [`ConditionalPick`](source/conditional-pick.d.ts) - Like `Pick` except it selects properties from a shape where the values extend the given `Condition` type.
- [`ConditionalExcept`](source/conditional-except.d.ts) - Like `Omit` except it removes properties from a shape where the values extend the given `Condition` type.
- [`UnionToIntersection`](source/union-to-intersection.d.ts) - Convert a union type to an intersection type.

### Miscellaneous

Expand Down
4 changes: 3 additions & 1 deletion source/set-optional.d.ts
@@ -1,3 +1,5 @@
import {Except} from './except';

/**
Create a type that makes the given keys optional. The remaining keys are kept as is. The sister of the `SetRequired` type.
Expand All @@ -23,7 +25,7 @@ type SomeOptional = SetOptional<Foo, 'b' | 'c'>;
*/
export type SetOptional<BaseType, Keys extends keyof BaseType = keyof BaseType> =
// Pick just the keys that are not optional from the base type.
Pick<BaseType, Exclude<keyof BaseType, Keys>> &
Except<BaseType, Keys> &
// Pick the keys that should be optional from the base type and make them optional.
Partial<Pick<BaseType, Keys>> extends
// If `InferredType` extends the previous, then for each key, use the inferred type key.
Expand Down
4 changes: 3 additions & 1 deletion source/set-required.d.ts
@@ -1,3 +1,5 @@
import {Except} from './except';

/**
Create a type that makes the given keys required. The remaining keys are kept as is. The sister of the `SetOptional` type.
Expand All @@ -23,7 +25,7 @@ type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
*/
export type SetRequired<BaseType, Keys extends keyof BaseType = keyof BaseType> =
// Pick just the keys that are not required from the base type.
Pick<BaseType, Exclude<keyof BaseType, Keys>> &
Except<BaseType, Keys> &
// Pick the keys that should be required from the base type and make them required.
Required<Pick<BaseType, Keys>> extends
// If `InferredType` extends the previous, then for each key, use the inferred type key.
Expand Down
58 changes: 58 additions & 0 deletions source/union-to-intersection.d.ts
@@ -0,0 +1,58 @@
/**
Convert a union type to an intersection type using [distributive conditional types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
Inspired by [this Stack Overflow answer](https://stackoverflow.com/a/50375286/2172153).
@example
```
import {UnionToIntersection} from 'type-fest';
type Union = {the(): void} | {great(arg: string): void} | {escape: boolean};
type Intersection = UnionToIntersection<Union>;
//=> {the(): void; great(arg: string): void; escape: boolean};
```
A more applicable example which could make its way into your library code follows.
@example
```
import {UnionToIntersection} from 'type-fest';
class CommandOne {
commands: {
a1: () => undefined,
b1: () => undefined,
}
}
class CommandTwo {
commands: {
a2: (argA: string) => undefined,
b2: (argB: string) => undefined,
}
}
const union = [new CommandOne(), new CommandTwo()].map(instance => instance.commands);
type Union = typeof union;
//=> {a1(): void; b1(): void} | {a2(argA: string): void; b2(argB: string): void}
type Intersection = UnionToIntersection<Union>;
//=> {a1(): void; b1(): void; a2(argA: string): void; b2(argB: string): void}
```
*/
export type UnionToIntersection<Union> = (
// `extends any` is always going to be the case and is used to convert the
// `Union` into a [distributive conditional
// type](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
Union extends unknown
// The union type is used as the only argument to a function since the union
// of function arguments is an intersection.
? (distributedUnion: Union) => void
// This won't happen.
: never
// Infer the `Intersection` type since TypeScript represents the positional
// arguments of unions of functions as an intersection of the union.
) extends ((mergedIntersection: infer Intersection) => void)
? Intersection
: never;
9 changes: 9 additions & 0 deletions test-d/union-to-intersection.ts
@@ -0,0 +1,9 @@
import {expectType} from 'tsd';
import {UnionToIntersection} from '..';

declare const intersection1: UnionToIntersection<{a: string} | {b: number}>;
expectType<{a: string; b: number}>(intersection1);

// Creates a union of matching properties.
declare const intersection2: UnionToIntersection<{a: string} | {b: number} | {a: () => void}>;
expectType<{a: string | (() => void); b: number}>(intersection2);

0 comments on commit bed43c1

Please sign in to comment.