Skip to content

add SimplifyUnion type - #1268

Closed
benzaria wants to merge 9 commits into
sindresorhus:mainfrom
benzaria:simplify-union
Closed

benzaria wants to merge 9 commits into
sindresorhus:mainfrom
benzaria:simplify-union

Conversation

@benzaria

Copy link
Copy Markdown
Contributor

closes #1174

Comment thread test-d/simplify-union.ts Outdated
//=> 0 | "a" | null | ['b', 1n] | (() => void) | {[x: string]: number; b: number}

expectType<SimplifiedUnion>({} as UnSimplifiedUnion);
// Checking all types are preserved.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird comment placement.

Comment thread source/simplify-union.d.ts Outdated
@see {@link Simplify}
@category Union
*/
export type SimplifyUnion<Union> = {[Member in Union as typeof tag]: Member}[typeof tag];

@som-sm som-sm Oct 13, 2025 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem to work if you pass the union directly instead of storing it in a type alias first.

type U1 = {a: 1};
type U2 = {b: 2};

type UnSimplifiedUnion = U1 | U2;
//=> U1 | U2 <- Not simplified

type SimplifiedUnion = SimplifyUnion<U1 | U2>;
//=> U1 | U2 <- Still, not simplified

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird, i will look into that!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@som-sm It seems the issue is that U1 and U2 aren’t unions themselves — they’re being recognized as standalone types.

type U1 = {a: 1};
declare const u1: U1;
// => Displays as `const u1: U1` instead of `const u1: {a: 1}`

To properly demonstrate the effect, the example should use actual unions, as shown below:

type U1 = {a: 1} | {c: 3};
type U2 = {b: 2} | 'a';

type UnSimplifiedUnion = U1 | U2;
// => U1 | U2 (not simplified)

type SimplifiedUnion = SimplifyUnion<U1 | U2>;
// => {a: 1} | {c: 3} | {b: 2} | "a"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the issue is that U1 and U2 aren’t unions themselves

But U1 | U2 is a union, so it should work ideally.

The existing Simplify type that we have works:

type U1 = {a: 1};
type U2 = {b: 2};

type UnSimplifiedUnion = U1 | U2;
//=> U1 | U2 <- Not simplified

type SimplifiedUnion = Simplify<U1 | U2>;
//=> {a: 1} | {b: 2} <- Simplified

@benzaria What problem does SimplifyUnion solve that Simplify doesn't? Is it just that the former handles functions and latter doesn't, if so, this should work:

type Simplify<T> = T extends Function ? T : {[P in keyof T]: T[P]};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, my first attempt was the one below, which I originally made for #1178:

type Simplify<Type> = ConditionalSimplify<
	Type,
	NonRecursiveType | Set<unknown> | Map<unknown, unknown>,
	object
>;

It effectively does the same as your suggestion. However, what ended up happening is that some types depending on Simplify started to break — such as MergeDeep, ApplyDefaultOptions, and other types that rely on it.

That led me down a bit of a rabbit hole trying to fix MergeDeep and ApplyDefaultOptions. I was able to get ApplyDefaultOptions working again, but MergeDeep still needs a refactor, since it uses a fairly complex merge strategy for options instead of leveraging ApplyDefaultOptions.

My suggestion for now is to introduce both an internal and external version of Simplify, for better separation of use cases. This way, we can address the current limitation in Simplify without breaking half of the library:

export type _Simplify<T> = { [K in keyof T]: T[K] } & {};

export type Simplify<Type> = ConditionalSimplify<
	Type,
	NonRecursiveType | Set<unknown> | Map<unknown, unknown>,
	object
>;

@sindresorhus @som-sm What are your thoughts on this approach?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SimplifyUnion

3 participants