Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UnionToIntersection type #89

Merged
merged 5 commits into from Mar 9, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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) - Converts 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
59 changes: 59 additions & 0 deletions source/union-to-intersection.d.ts
@@ -0,0 +1,59 @@
/**
A helpful utility which maps 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 a [stack overflow answer](https://stackoverflow.com/a/50375286/2172153).

@example

```ts
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 it's way into your library code follows.

@example

```ts
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 any
ifiokjr marked this conversation as resolved.
Show resolved Hide resolved
// 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;
ifiokjr marked this conversation as resolved.
Show resolved Hide resolved
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);