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

Omit behaves poorly with unions #39556

Closed
bakkot opened this issue Jul 10, 2020 · 5 comments
Closed

Omit behaves poorly with unions #39556

bakkot opened this issue Jul 10, 2020 · 5 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@bakkot
Copy link
Contributor

bakkot commented Jul 10, 2020

TypeScript Version: 3.9.2 (also v4.0.0-beta)

Search Terms: omit, pick, union

Code

type StringTok = { name: 'string', location: number, contents: string }
type NumberTok = { name: 'number', location: number, value: number }

type Tok = StringTok | NumberTok

type UnlocatedTok = Omit<Tok, 'location'>

let a: UnlocatedTok = { name: 'number', value: 1 }

let b: UnlocatedTok = { name: 'number' }

Expected behavior:
No type error on the let a, type error on the let b.

Actual behavior:

Type error on the let a:

Type '{ name: "number"; value: number; }' is not assignable to type 'Pick<Tok, "name">'.
Object literal may only specify known properties, and 'value' does not exist in type 'Pick<Tok, "name">'.(2322)

No type error on the let b.

Specifically, it is surprising to me that, contrary to its description, the result of Omit<T, K> is not the type which contains the members of T except without the properties in K, but instead some other type.

Playground Link: Link

Related Issues: None that I could find.

@IllusionMH
Copy link
Contributor

Duplicate of #31501 search terms: Omit union

It's working as intended.
Omit isn't distributive over unions and if you check implementation its Pick<T, Exclude<keyof T, K>
keyof for union will return only properties that are available in all union members so it will be 'name' | 'location' and with 'location' excluded only one key is left for Pick

You need to use conditional types to process each union member separately type SimpleUnionOmit<T, K extends string | number | symbol> = T extends unknown ? Omit<T, K> : never; of find more complete constrained approach.

Example

@bakkot
Copy link
Contributor Author

bakkot commented Jul 10, 2020

Hm, ok. Thanks for the workaround. Is it perhaps worth updating the documentation to explain that Omit doesn't do the thing one might expect and to link the workaround which does the thing one would actually want? I would not have been able to come up with that solution on my own.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jul 13, 2020
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@everdimension
Copy link

Uhm, how is this "working as intended"?

@jhunterkohler
Copy link

jhunterkohler commented Apr 26, 2022

@everdimension

The Omit type intentionally does not distribute unions. When a type parameter is evaluated against an extends clause, this changes the evaluation from being an operation on one type, to the union of the operation on each element within the original union. This would be a distinctly different helper.

See the following for example of distribution:

type ToArrayDistributive<T> = T extends any ? T[] : never;
type ToArrayNonDistributive<T> = T[];

type ExampleDistributive = ToArrayDistributive<string | number>; //  string[] | number[]
type ExampleNonDistributive = ToArrayNonDistributive<string | number>; // (string | number)[]

To distribute the type parameter of Omit, you could do the following:

type OmitDistributive<T, K extends keyof any> = T extends any ? Omit<T, K> : never;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

6 participants