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 DistributedOmit type #820

Merged
merged 3 commits into from Mar 18, 2024

Conversation

henriqueinonhe
Copy link
Contributor

@henriqueinonhe henriqueinonhe commented Feb 25, 2024

(Sorry for the delay)

Implements DistributedOmit, that omits keys from a type, distributing the operation over a union, while also offering autocompletion for keys that can be omitted.

Related to #132.

Typescript's Omit doesn't distribute over unions, which causes the following situation:

type A = {
  discriminant: "A";
  foo: string;
  a: number;
};

type B = {
  discriminant: "B";
  foo: string;
  b: string;
};

type Union = A | B;

type OmittedUnion = Omit<Union, "foo">; //==> { discriminant: "A" | "B" }

const omittedUnion: OmittedUnion = createOmittedUnion();

if(omittedUnion.discriminant === "A") {
  // We would like to narrow `omittedUnion`'s type
  // to `A` here, but we can't because `Omit`
  // doesn't distribute over unions.

  omittedUnion.a; //==> Error, `a` is not a property of `{ discriminant: "A" | "B" }`
}

While Except solves this problem, it restricts the keys you can omit to the ones that are present in ALL union members, where DistributedOmit allows you to omit keys that are present in ANY union member.

type A = {
  discriminant: "A";
  foo: string;
  a: number;
};

type B = {
  discriminant: "B";
  foo: string;
  bar: string;
  b: string;
};

type C = {
  discriminant: "C";
  bar: string;
  c: boolean;
};

// Notice that `foo` exists in `A` and `B`, but not in `C`, and
// `bar` exists in `B` and `C`, but not in `A`.

type Union = A | B | C;

type OmittedUnion = DistributedOmit<Union, "foo" | "bar">;

const omittedUnion: OmittedUnion = createOmittedUnion();

if(omittedUnion.discriminant === "A") {
  omittedUnion.a; //==> OK
  omittedUnion.foo; //==> Error, `foo` is not a property of `{ discriminant: "A"; a: string; }`
  omittedUnion.bar; //==> Error, `bar` is not a property of `{ discriminant: "A"; a: string; }`
}

/**
Omits keys from a type, distributing the operation over a union.

TypeScript's `Omit` doesn't distribute over unions, which causes the following situation:
Copy link
Owner

Choose a reason for hiding this comment

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

It would be useful to have a description in text, not just examples. Examples take longer to understand.

Here's a suggested text. Can you check that it's correct?

Suggested change
TypeScript's `Omit` doesn't distribute over unions, which causes the following situation:
TypeScript's `Omit` doesn't distribute over unions, leading to the erasure of unique properties from union members when omitting keys. This creates a type that only retains properties common to all union members, making it impossible to access member-specific properties after the Omit. Essentially, using `Omit` on a union type merges the types into a less specific one, hindering type narrowing and property access based on discriminants. This type solves that.

Feel free to edit it as you see fit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed

@sindresorhus
Copy link
Owner

Fixes #132.

This doesn't fix that issue as it's missing DistributedPick.

@sindresorhus sindresorhus merged commit bc49577 into sindresorhus:main Mar 18, 2024
6 checks passed
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.

None yet

2 participants