Repository navigation
add stricter Omit helper type #30825
Description
Activity
- addedIn DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript
on Apr 10, 2019 I was disappointed when I saw that the
Omittype added in #30552 was not the strict one suggested in #30455, and what most third-party type libraries provide. Like Sean Kelley (@seansfkelley) mentioned, there are many benefits of making it strict. One additional benefit is the ability for everyone usingOmitin these type libraries to use the built-in one instead. This will not be possible if the built-in one is not strict.Reacted by Jarek Radosz, Veniamin Krol, Andrii Dieiev, Christophe Hurpeau, Timo Lins, ExE Boss, Shu Uesugi, Seth Holladay, Bo Lingen, Leo Y. Li and 16 moreReacted by CondorHeroCurrently, permissive Omit acts as a "barrier" that prevents rename refactors from passing through
Could this be fixed instead?
Currently, permissive Omit acts as a "barrier" that prevents rename refactors from passing through
Could this be fixed instead?
That would help a lot, I think, but doesn't catch cases where someone does a manual refactor or just outright typos a field name and ends up exposing the wrong type to a consumer (i.e., a type that has fields that should not be visible).
Edit: I will say, I actually don't understand the use-case for permissive
Omitat all -- I've never wanted it, but that might be a function of the fact that I'm not generally juggling genericextends stringomits (e.g.type Foo<T extends string> = Omit<Bar, T>) but rather literal omit (e.g.type Foo = Omit<Bar, "field">).Reacted by Jarek Radosz, Veniamin Krol, Sindre Sorhus, Christophe Hurpeau, ExE Boss, Morris Allison III, Leo Y. Li, Gareth Jones, Billy Janitsch, Gábor IMRE and 1 more- addedDeclinedThe issue was declined as something which matches the TypeScript visionThe issue was declined as something which matches the TypeScript vision
on Apr 16, 2019 DanielRosenwasser commented
on Apr 16, 2019 MemberMore actionsIt seems like the constrained
Omittype is going to make at least half of users unhappy based on declarations within DefinitelyTyped. We've decided to go with the more permissive built-in which your own constraints can build on.I wish I hadn't encouraged opening this issue in the first place. It just made the situation worse than before as the
Omitname is now taken by TS, so we have to rename our strict versions to prevent confusion...Reacted by Bo Lingen, Alex Silcock, Morris Allison III, Leo Y. Li, Mordy Tikotzky, George Kalpakas, Billy Janitsch, Felipe Nunes, Dylan Greene, Ruslan Fadeev and 3 moreReacted by Leo Y. Li and Lukas KlusisSamVerschueren commented
on Apr 17, 2019 More actionsIt seems like the constrained Omit type is going to make at least half of users unhappy based on declarations within DefinitelyTyped.
And now half of the users are unhappy because it's loose. I totally understand that decisions like this are tough, but you can't make everyone happy. Sometimes you have to make people unhappy for the greater good. And in my opinion, strict typing is the greater good.
The current situation is that everyone is still using a custom
Omit, e.g.StrictOmitwhich again should be defined everywhere. Hence, we're back to start.Reacted by Remy Rylan, Andrii Dieiev, ExE Boss, Nick Clifford, Bo Lingen, Sam A. Horvath-Hunt, Stéphane Le Dorze, Daniel Nixon, Leo Y. Li, Gareth Jones and 9 moreReacted by Stéphane Le Dorze, Gareth Jones and Duc PhanI like having a loose Omit option available, but I agree it would be nice to see
Omitstay consistent with how the community uses and understands it today. Providing both seems like a win-win.Maybe we could find an alternate name for it?
OmitIfExcludeRemoveWithout
Reacted by Dan M.RyanCavanaugh commented
on May 17, 2019 MemberMore actionsit would be nice to see Omit stay consistent with how the community uses and understands it today.
I have to clear up this misconception. There were 12 different definitions of
Omiton DT and the two most popular definitions differed on whether or not to constraint the key:(hit count, definition) 15 type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> 13 type Omit<T, K> = Pick<T, Exclude<keyof T, K>>; 11 type Omit<T, K extends keyof T> = Pick<T, ({ [P in keyof T]: P } & { [P in K]: never } & { [x: string]: never, [x: number]: never })[keyof T]>; 3 type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; 2 type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>; 2 type Omit<T, K> = { [key in Exclude<keyof T, K>]: T[key] }; 2 type Omit<T1, T2> = Pick<T1, Exclude<keyof T1, keyof T2>>; 1 type Omit<T, E extends keyof T> = { ... 1 type Omit<T, K extends keyof any> = T extends any ? Pick<T, Exclude<keyof T, K>> : never; 1 type Omit<T, K extends keyof T> = Pick<T, ({ [P in keyof T]: P } & { [P in K]: never } & { [x: string]: never })[keyof T]>; 1 type Omit<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never; 1 type Omit<T, K extends string> = Pick<T, Exclude<keyof T, K>>;
Reacted by Daniel Rosenwasser, Stephen Wicklund and Esa KoskinenReacted by Daniel Rosenwasser, Morris Allison III, Dmitry Mazurok, Arnau Sanchez Sala, Gábor IMRE, Jarod Burchill and Linus UnnebäckReacted by Leo Y. Li and George KalpakasRyanCavanaugh commented
on May 17, 2019 MemberMore actionsYou can pick at the numbers and try to declare a democratic majority or something, but the reality is that only one definition doesn't break a substantial portion of people.
Moreover there is nothing wrong with passing non-
keyof Targuments toK:type Omit1<T, K> = Pick<T, Exclude<keyof T, K>>; type Omit2<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; // Can't use Omit2 here declare function combineSpread<T1, T2>(obj: T1, otherObj: T2, rest: Omit1<T1, keyof T2>): void; type Point3d = { x: number, y: number, z: number }; declare const p1: Point3d; // OK combineSpread(p1, { x: 10 }, { y: 5, z: 2 }); combineSpread(p1, { x: 1, y: 3 }, { z: 2 }); // Error combineSpread(p1, { x: 10 }, { z: 2 });
Reacted by Daniel Rosenwasser, Dmitry Mazurok, Stephen Wicklund and Jarod BurchillReacted by Dan M.Moreover there is nothing wrong with passing non-keyof T arguments to
KThis is true, and why the original suggestion was worded to allow for "some other
Omit-like type". That said, as I noted in an earlier comment:I actually don't understand the use-case for permissive
Omitat allWhich is still mostly true, though your provided example seems like a thing I would eventually write at some point, I suppose. Currently, I use
type-zoo'sOmitStricteverywhere and therefore, the name collision doesn't matter to me, practically speaking.I always default to strict rather than lenient, and if the standard library doesn't want to, that's okay. I simply figured that if the standard library was going to try to be helpful by providing what until recently was a de facto community-standard type, it would want to address all the related use-cases, and I saw an opportunity to roll more of
type-zoointo the standard library where I think it would be very helpful.Reacted by Sindre Sorhus, Morris Allison III, Leo Y. Li and George KalpakasRyan Cavanaugh (@RyanCavanaugh) I appreciate that you want to look at the facts as opposed to passing opinion. Totally fair. That said, I want to make sure I'm following which packages you're talking about.
The two top packages I see,
type-festandtypical, both look like they require the key to be present (if I'm reading them correctly).Package Usage
Definitions
type-festdefinition:export type Omit<ObjectType, KeysType extends keyof ObjectType> = Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>;
typicaldefinition:export type Omit<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never;
Reacted by George Kalpakas, Elad Bezalel, DoZerg and MihaelReacted by George KalpakasReacted by George KalpakasYou can pick at the numbers and try to declare a democratic majority or something, but the reality is that only one definition doesn't break a substantial portion of people.
Ryan Cavanaugh (@RyanCavanaugh) I think this is a reasonable point.
9 remaining items
- added a commit that references this issue
on Jul 5, 2019 It seems like the constrained
Omittype is going to make at least half of users unhappy based on declarations within DefinitelyTyped.It looks like the typescript team might have accidentally found a technical solution to what is actually a people problem: what do users want this type to be? What are programmers trying to express when they write
Omit<Props, 'className'>?There are plenty of conceivable reasons an
Omitin DT might not constrain the keys. One is low-quality or under-maintained packages. Another is that the definition was written for TypeScript 2.0 which didn't havekeyofyet. I think the latest version of the stdlib should be held to a higher standard than miscellaneous DT packages. I hope we can find a path forward towards improving this situation.Reacted by Matthias Heinisch and Gareth JonesJust an FYI: WebStorm 2019.2.2 will ship WEB-40482 that'll mean it will provide intellisense for the second argument of
Omitbased off the first argument:Artificially supplied Omit's second argument with the expected type containing all the keys of the first argument.
Now completion, go to declaration, find usages and rename will work for properties referenced in Omit's second argument.
Also, massive shoutout to Anton of the WebStorm team, for his amazing work improving the TypeScript side of things, and for having to put up w/ me throwing tons of TS edge-cases at him 😂
Reacted by Leo Y. Li, Andrey and Colin Wirt- addedDeclinedThe issue was declined as something which matches the TypeScript visionThe issue was declined as something which matches the TypeScript visionand removedIn DiscussionNot yet reached consensusNot yet reached consensus
on Aug 21, 2019 RyanCavanaugh commented
on Aug 21, 2019 MemberMore actionsAfter much discussion, we think libraries providing their own "stricter" versions of Omit (as well as
Excludeand other friends) is preferable. TheStrictversions of these types are very infectious in terms of forcing upstream constraints and it's not clear that people will wisely choose between them. This can induce some real friction if not carefully managed.This is actually more true now that
Omitis in the lib - if we pickOmitStrict(for which I'm sure there are in turn multiple definitions to pick from) then there's not much in terms of good names left in userspace. There are a few ways to write all of these helper types and letting developers choose the one that matches their own definition of "strict" is the option that's going to maximize individual freedom.The reaction here and on Twitter to our (entirely defensible IMO) choice of the definition of
Omitshows why we're not really excited about picking anOmitStrictthat is going to anger some 30-70% of developers because we don't pick their preferred definition. It appears it's only really a good idea to add things to the lib if its definition is entirely unambiguous, andOmitStrictdoesn't fit that criteria. In retrospect, neither didOmit, and perhaps we should have just left it out.Go forth, developers, with your chosen definitions of
OmitStricthappy and secure in the knowledge that TS isn't going to stomp on that name 😬Reacted by Joel Purra and Jarod BurchillReacted by Sindre Sorhus, Fernando Rojo, Szymon Marczak, Tim Whitbeck, Tony K, Sergey Cherepanov, Lukas Klusis, Hugo Lopes, Sushruth Sastry, Julius Marozas and 18 moreReacted by pierre, Hugo Lopes and AlexisReacted by Richard WiseApologies for resurrecting this thread, but I did stumble upon a behavioral difference that made me very happy for my usage of
OmitStrict. I respect the team's decision to not include it in the standard library, but I thought this example was worth having recorded somewhere, and I don't believe it's been mentioned in previous comments.I'll let the example speak for itself:
type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never; interface Foo { foo: string; } // Silently produces dangerous {} type. type T1 = Omit<Foo | undefined, "bar">; // Complains, regardless of what you provide to the second type parameter. type T2 = OmitStrict<Foo | undefined, "bar">; // Complains like you would expect. type T3 = OmitStrict<NonNullable<Foo | undefined>, "bar">; // Ah, type safety. type T4 = OmitStrict<NonNullable<Foo | undefined>, "foo">;
Reacted by Nelson Martell, Martin Begán, Duy Bao Nguyen, gebsl, Richard Wise, Mihael and Bennett DamsReacted by Nelson Martell, gebsl and mikeKlechThanks for the
OmitStricthelper, Sean Kelley (@seansfkelley).Reacted by Nelson MartellRyan Cavanaugh (@RyanCavanaugh),
I take your point that no matter what you do as far as
Omitvs.OmitStrict, people are going to complain, but here's one more suggestion to mull over:Could there not be a TSConfig option
strictOmit? I know there are already a million switches in TSConfig, but this would allow people to opt into a type-checked Omit if that's already how they're using it in their project.Happy to raise this as a separate issue if you think it's worth considering and doesn't already exist.
Dan M. (@devuxer) a flag would mean that you could change the behavior of types on a third-party project, which doesn't seem like it'd work out too well.
Reacted by Ryan Cavanaugh and Nelson MartellRyanCavanaugh commented
on May 20, 2021 MemberMore actionsWhat Jordan Harband (@ljharb) said. Flags like that are just asking for errors to appear ex nihilo in your imported
.d.tsfiles, which no one wants.- added a commit that references this issue
on Jul 18, 2021 Couldn't
Omitjust accept another optional argumentstrict = false? That way it remains backward compatible and users wouldn't need to create a wrapper (or worse, import a 3rd party library for justStrictOmit)PoC:
type Omit< T, K extends S extends true ? keyof T : keyof any, S extends boolean = false > = Pick<T, Exclude<keyof T, K>>; interface X { foo: string; bar: string; } type A = Omit<X, "baz">; // Backward compatible, no errors type B = Omit<X, "baz", true>; // Error: Type '"baz"' does not satisfy the constraint 'keyof X'
Reacted by Dan M.- locked as resolved and limited conversation to collaborators
on Nov 29, 2021


Search Terms
omit strict
Suggestion
The new
Omittype does not restrict the omitted keys to be keys actually present on the given type. There should be some avenue to express "omit, but only with keys that are present", likely either a change toOmitor some otherOmit-like type.Use Cases
Copied from pelotom/type-zoo#31.
The benefit that a stricter type has is primarily:
Currently, permissive Omit acts as a "barrier" that prevents rename refactors from passing through, which means that any such refactor generates whole bunches of errors that have to be manually fixed. If the field in question is optional, this can actually introduce bugs.
And some further color:
I generally use
Omitwith string literal unions (as opposed to, say, generic types thatextends string), because I often use them for defining higher-order React components that wrap another component except for this one prop. As such, in my use case, I never want a permissiveOmit.Examples
Checklist
My suggestion meets these guidelines: