-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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 stricter Omit helper type #30825
Comments
I was disappointed when I saw that the |
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 |
It seems like the constrained |
I wish I hadn't encouraged opening this issue in the first place. It just made the situation worse than before as the |
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 |
I like having a loose Omit option available, but I agree it would be nice to see Maybe we could find an alternate name for it?
|
I have to clear up this misconception. There were 12 different definitions of (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>>; |
You 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- 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 }); |
This is true, and why the original suggestion was worded to allow for "some other
Which is still mostly true, though your provided example seems like a thing I would eventually write at some point, I suppose. Currently, I use 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 |
@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, Package UsageDefinitions
export type Omit<ObjectType, KeysType extends keyof ObjectType> = Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>;
export type Omit<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never; |
@RyanCavanaugh I think this is a reasonable point. |
Also worth noting that, historically, the TypeScript docs have always shown examples as the strict version.
(from https://www.typescriptlang.org/docs/handbook/advanced-types.html) |
All that said, I agree that |
This is really misfortune to be declined. Can someone tell me what is the motivation why we add In the release note, it's been said that TS core team finally realize it's so common so hoping to standardize its implementation for the community. I see it's a great move to make our life easier, but then I see it just get shipped sloppily. 🙁 @RyanCavanaugh, I did surprise you dig into the DT and count these implementations and use it justify something that is shaky. I do appreciate this empathy to ensure not making too much noisy while ensure everyone can be happy whenever it is possible. But I do hope you and other members in the core team to rethink about the Also, we can consider
It looks pretty nature and intuitive when we see them together, isn't it? But how come When we make comparison, compare |
Uh... I see where this weird (Omit v.s. Exclude/Extract) comparison came from (#30738). I think it is not good to post my comment there so I did it here. Here is how I justify the API surface for how the built-in helper types could be:
For the above relationship and reason, I really believe UPDATE: why this is important: In react, we sometimes want to write glue component to bridge two libraries, then we will have: type ConstrainedComponentProps = BasicProps &
Omit<LibAProps, 'A' | 'B'> &
Pick<LibBProps, 'C' | 'D'> That is to say, |
Personally I agree that
which only becomes a reality when those people both update to 3.5, and remove their definition of Case and point, I can do this no problem:
I mean given that you have to update eitherway, you could have easily made both parties happy by implementing
That's assuming that everyone not using For example, it's quite reasonable the people using However, in saying all this, for me my whole actual problem with having I've found that in WebStorm intellisense if you have Off the top of my head (which is a head that has little idea how the internals of TS work for generating intellisense), could the intellisense somehow ignore That wouldn't be a breaking change, since (at least imo) what is suggested for Finally, a crazy idea: what about |
is it possible that a future release ( |
If you want to enforce that no-one on your project accidentally uses the built-in {
rules: {
'@typescript-eslint/ban-types': [
'error',
{
types: {
Omit: "Prefer `OmitStrict`.",
},
},
],
}
} |
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 There are plenty of conceivable reasons an |
Just an FYI: WebStorm 2019.2.2 will ship WEB-40482 that'll mean it will provide intellisense for the second argument of
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 😂 |
After much discussion, we think libraries providing their own "stricter" versions of Omit (as well as This is actually more true now that The reaction here and on Twitter to our (entirely defensible IMO) choice of the definition of Go forth, developers, with your chosen definitions of |
Apologies for resurrecting this thread, but I did stumble upon a behavioral difference that made me very happy for my usage of 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">; |
Thanks for the |
I take your point that no matter what you do as far as Could there not be a TSConfig option Happy to raise this as a separate issue if you think it's worth considering and doesn't already exist. |
@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. |
What @ljharb said. Flags like that are just asking for errors to appear ex nihilo in your imported |
Couldn't 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' |
Search Terms
omit strict
Suggestion
The new
Omit
type 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 toOmit
or 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
Omit
with 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:
The text was updated successfully, but these errors were encountered: