Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -167,6 +167,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 {LastOfUnion} from './source/last-of-union.d.ts';

// Template literal types
export type {CamelCase, CamelCaseOptions} from './source/camel-case.d.ts';
Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ Click the type names for complete docs.
- [`ConditionalSimplify`](source/conditional-simplify.d.ts) - Simplifies a type while including and/or excluding certain types from being simplified.
- [`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`.
- [`LastOfUnion`](source/last-of-union.d.ts) - Return a member of a union type. Order is not guaranteed.

### Type Guard

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

/**
Return a member of a union type. Order is not guaranteed.
Returns `never` when the input is `never`.

@see https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468375328

Use-cases:
- Implementing recursive type functions that accept a union type.
- Reducing a union one member at a time, for example when building tuples.

It can detect a termination case using {@link IsNever `IsNever`}.

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

export type UnionToTuple<T, L = LastOfUnion<T>> =
IsNever<T> extends false
? [...UnionToTuple<Exclude<T, L>>, L]
: [];
```

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

type Last = LastOfUnion<1 | 2 | 3>;
//=> 3

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

@category Type
*/
export type LastOfUnion<T> =
[T] extends [never]
? never
: UnionToIntersection<T extends any ? () => T : never> extends () => (infer R)
? R
: never;

export {};
16 changes: 1 addition & 15 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 {LastOfUnion} from './last-of-union.d.ts';

/**
Convert a union type into an unordered tuple type of its elements.
Expand Down
43 changes: 43 additions & 0 deletions test-d/last-of-union.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {expectType} from 'tsd';
import type {LastOfUnion, IsAny, IsUnknown, IsNever} from '../index.d.ts';

// `LastOfUnion` distinguishes between different modifiers.
type UnionType = {a: 0} | {b: 0} | {a?: 0} | {readonly a?: 0} | {readonly a: 0};
expectType<true>({} as LastOfUnion<UnionType> extends UnionType ? true : false);
expectType<false>({} as UnionType extends LastOfUnion<UnionType> ? true : false);

// `never` acts as a termination condition with `IsNever`.
expectType<never>({} as LastOfUnion<never>);

expectType<true>({} as IsUnknown<LastOfUnion<unknown>>);
expectType<true>({} as IsAny<LastOfUnion<any>>);

// Ensure a loop of `LastOfUnion` returns all elements.
type UnionToTuple<T, L = LastOfUnion<T>> =
IsNever<T> extends false
? [...UnionToTuple<ExcludeExactly<T, L>>, L]
: [];

type MatchOrNever<A, B> =
[unknown, B] extends [A, never]
? A
// This equality code base below doesn't work if `A` is `unknown` and `B` is `never` case.
// So this branch should be wrapped to take care of this.
: (<G>() => G extends A & G | G ? 1 : 2) extends (<G>() => G extends B & G | G ? 1 : 2)
? never
: A;

type ExcludeExactly<UnionU, DeleteT> =
LastOfUnion<DeleteT> extends infer D
? true extends IsNever<D>
? UnionU
: ExcludeExactly<_ExcludeExactly<UnionU, D>, _ExcludeExactly<DeleteT, D>>
: never;

type _ExcludeExactly<UnionU, DeleteT> =
UnionU extends unknown // Only for union distribution.
? MatchOrNever<UnionU, DeleteT>
: never;

type DifferentModifierUnion = {readonly a: 0} | {a: 0};
expectType<DifferentModifierUnion>({} as UnionToTuple<DifferentModifierUnion>[number]);