Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export type {IsNullable} from './source/is-nullable.d.ts';
export type {TupleOf} from './source/tuple-of.d.ts';
export type {ExclusifyUnion} from './source/exclusify-union.d.ts';
export type {ArrayReverse} from './source/array-reverse.d.ts';
export type {UnionMember} from './source/union-member.d.ts';

// Template literal types
export type {CamelCase, CamelCaseOptions} from './source/camel-case.d.ts';
Expand Down
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ Click the type names for complete docs.
- [`ConditionalSimplifyDeep`](source/conditional-simplify-deep.d.ts) - Recursively simplifies a type while including and/or excluding certain types from being simplified.
- [`ExclusifyUnion`](source/exclusify-union.d.ts) - Ensure mutual exclusivity in object unions by adding other members’ keys as `?: never`.
- [`Optional`](source/optional.d.ts) - Create a type that represents either the value or `undefined`, while stripping `null` from the type.
- [`UnionMember`](source/union-member.d.ts) - Returns an arbitrary member of a union type.

### Type Guard

Expand Down Expand Up @@ -358,6 +359,8 @@ Click the type names for complete docs.
- `Maybe`, `Option` - See [`Optional`](source/optional.d.ts)
- `MaybePromise` - See [`Promisable`](source/promisable.d.ts)
- `ReadonlyTuple` - See [`TupleOf`](source/tuple-of.d.ts)
- `LastOfUnion` - See [`UnionMember`](source/union-member.d.ts)
- `FirstOfUnion` - See [`UnionMember`](source/union-member.d.ts)

## Tips

Expand Down
65 changes: 65 additions & 0 deletions source/union-member.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type {UnionToIntersection} from './union-to-intersection.d.ts';
import type {IsNever} from './is-never.d.ts';

/**
Returns an arbitrary member of a union type.

Use-cases:
- Implementing recursive type functions that accept a union type.

@example
```
import type {UnionMember, IsNever} from 'type-fest';

type UnionLength<T, Acc extends any[] = []> =
UnionMember<T> extends infer Member
? IsNever<Member> extends false
? UnionLength<Exclude<T, Member>, [...Acc, Member]>
: Acc['length']
: never;

type T1 = UnionLength<'foo' | 'bar' | 'baz'>;
//=> 3

type T2 = UnionLength<{a: string}>;
//=> 1
```

- Picking an arbitrary member from a union

@example
```
import type {UnionMember, Primitive, LiteralToPrimitive} from 'type-fest';

type IsHomogenous<T extends Primitive> = [T] extends [LiteralToPrimitive<UnionMember<T>>] ? true : false;

type T1 = IsHomogenous<1 | 2 | 3 | 4>;
//=> true

type T2 = IsHomogenous<'foo' | 'bar'>;
//=> true

type T3 = IsHomogenous<'foo' | 'bar' | 1>;
//=> false
```

Returns `never` when the input is `never`.

@example
```
import type {UnionMember} from 'type-fest';

type LastNever = UnionMember<never>;
//=> never
```

@category Type
*/
export type UnionMember<T> =
IsNever<T> extends true
? never
: UnionToIntersection<T extends any ? () => T : never> extends () => (infer R)
? R
: never;

export {};
18 changes: 2 additions & 16 deletions source/union-to-tuple.d.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
import type {IsNever} from './is-never.d.ts';
import type {UnionToIntersection} from './union-to-intersection.d.ts';

/**
Returns the last element of a union type.

@example
```
type Last = LastOfUnion<1 | 2 | 3>;
//=> 3
```
*/
type LastOfUnion<T> =
UnionToIntersection<T extends any ? () => T : never> extends () => (infer R)
? R
: never;
import type {UnionMember} from './union-member.d.ts';

/**
Convert a union type into an unordered tuple type of its elements.
Expand Down Expand Up @@ -50,7 +36,7 @@ const petList = Object.keys(pets) as UnionToTuple<Pet>;

@category Array
*/
export type UnionToTuple<T, L = LastOfUnion<T>> =
export type UnionToTuple<T, L = UnionMember<T>> =
IsNever<T> extends false
? [...UnionToTuple<Exclude<T, L>>, L]
: [];
Expand Down
36 changes: 36 additions & 0 deletions test-d/union-member.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {expectType} from 'tsd';
import type {UnionMember, IsNever, IsEqual} from '../index.d.ts';

expectType<false>({} as boolean extends UnionMember<boolean> ? true : false);
expectType<false>({} as (1 | 'foo' | 'bar') extends UnionMember<1 | 'foo' | 'bar'> ? true : false);
expectType<false>({} as ({foo: string} | {bar: number}) extends UnionMember<{foo: string} | {bar: number}> ? true : false);

expectType<number>({} as UnionMember<number>);
expectType<string>({} as UnionMember<string>);
expectType<bigint>({} as UnionMember<bigint>);
expectType<true>({} as UnionMember<true>);
expectType<false>({} as UnionMember<false>);
expectType<null>({} as any as UnionMember<null>);
expectType<undefined>({} as any as UnionMember<undefined>);

expectType<never>({} as UnionMember<never>);
expectType<unknown>({} as UnionMember<unknown>);
expectType<any>({} as UnionMember<any>);

// `WrapMemberInTuple` ensures `UnionMember` selects exactly one member at a time.
type WrapMemberInTuple<T, L = UnionMember<T>> =
IsNever<T> extends false
? WrapMemberInTuple<Exclude<T, L>> | [L]
: never;
expectType<[1] | [2] | [3]>({} as WrapMemberInTuple<1 | 2 | 3>);
expectType<['foo'] | ['bar'] | ['baz']>({} as WrapMemberInTuple<'foo' | 'bar' | 'baz'>);
expectType<[1] | ['foo'] | [true] | [100n] | [null] | [undefined]>(
{} as WrapMemberInTuple<1 | 'foo' | true | 100n | null | undefined>,
);
expectType<[{a: string}] | [{b: number}]>({} as WrapMemberInTuple<{a: string} | {b: number}>);

type UnionType = {a: 0} | {readonly a: 0};
type PickedUnionMember = UnionMember<UnionType>;
// We can't use `UnionType extends PickedUnionMember ? true : false` for testing here,
// because that would always be `true` as `UnionType` extends both of its members individually.
expectType<false>({} as IsEqual<UnionType, PickedUnionMember>);