-
Notifications
You must be signed in to change notification settings - Fork 12
Use keyof object in Omit for better type checking #42
Conversation
@vlazh could you please explain what's the benefit? Now I can do type A = { a: number; c: string }
type B = { a: boolean; b: string }
type C = Omit<B, keyof A> with this change I must do type A = { a: number; c: string }
type B = { a: boolean; b: string }
type C = Omit<B, keyof A & keyof B>
what's the use case which led to this change? |
type A = { aa: number; cc: string }
type B = Omit<A, 'a'> // compiler can't check that `a` don't belong to `A`. I have issue just with this case type A = { a: number; c: string }
type B = { a: boolean; b: string }
type C = Omit<B, keyof A> It feels like an exclusion. What if type ExcludeKeys<A extends object, K extends string | number | symbol> = Pick<A, Exclude<keyof A, K>>;
type C = ExcludeKeys<B, keyof A> Or may be extend type Omit<
A extends object,
K extends undefined extends D ? keyof A : object,
D extends keyof K = any
> = Pick<A, Exclude<keyof A, K extends keyof A ? K : D>>;
type C = Omit<B, A, keyof A>;
// or
type C = Omit<B, 'a'>; |
More strictly: type Omit<
A extends object,
K extends undefined extends D
? keyof A
: (Extract<keyof A, keyof K> extends never ? never : object),
D extends keyof K = any
> = Pick<A, Exclude<keyof A, K extends keyof A ? K : D>>;
type A = { a: number; c: string }
type B = { a: boolean; b: string }
type D = { d: number; e: string }
type C = Omit<B, A, keyof A>; // OK
type C = Omit<B, D, keyof D>; // Error: D has no any intersections with B, so Omit is unnecessary. |
And a little bit more strictly: type Omit<
A extends object,
K extends undefined extends D
? keyof A
: (Extract<keyof A, D> extends never ? never : Pick<K, Extract<keyof A, D>>),
D extends keyof K = any
> = Pick<A, Exclude<keyof A, K extends keyof A ? K : D>>;
type A = { a: number; c: string }
type B = { a: boolean; b: string }
type D = { d: number; e: string }
type C = Omit<B, 'a'>; // OK. Exclude `a` from `B`
type C = Omit<B, A, keyof A>; // OK. Exclude all intersections with `A`
// but
type C = Omit<B, A, 'c'>; // Error: `c` not containt in `B`
type C = Omit<B, D, keyof D>; // Error: `D` has no any intersections with `B`, so `Omit` is unnecessary. |
I found simpler solution but without autocomplete support: type Omit<
A extends object,
K extends Extract<keyof A, K> extends never ? never : PropertyKey
> = Pick<A, Exclude<keyof A, K>>;
type A = { a: number; c: string }
type B = { a: boolean; b: string }
type D = { d: number; e: string }
type C = Omit<B, 'a'>; // OK. Exclude `a` from `B`
type C = Omit<B, keyof A>; // OK. Exclude all intersections with `A`
// but
type C = Omit<B, 'c'>; // Error: `c` not containt in `B`
type C = Omit<B, keyof D>; // Error: `D` has no any intersections with `B`, so `Omit` is unnecessary. |
@vlazh Looks interesting but still could you please describe the use case? It looks ok to me that if you're trying to exclude a non-existent key from an object then you get the same object without any errors, don't you think? |
When I refactor code, rename or delete properties, I can't easily discover wrong exclusions. It's very handy when compiler can handle it for me. Autocomplete is very handy too. What the benefit of exclusion of nonexistent keys? |
So I guess it's more about developer experience/polishing the code than fixing some type-related issues, right? |
I think it is type-related issue. Typescript is for type checking, right? Why not use this power? Diff uses known keys and it's not bad. |
I agree, I just mean that it's more an improvement than a fix. Moreover it's a breaking change according to @gcanti comment. That's why I asked for the use case. UPD: We use |
Yeah, it might be problems for many code bases. So perhaps I should use the general implementation. |
It is all I have been able to think for less pain of using suggested type Omit<
A extends object,
K extends Extract<K, K> extends keyof A
? keyof A
: (Extract<keyof A, keyof K> extends never ? never : Pick<K, Extract<keyof A, keyof K>>)
> = Pick<A, Exclude<keyof A, K extends keyof A ? K : keyof K>>;
type A = { a: number; c: string }
type B = { a: boolean; b: string }
type D = { d: number; e: string }
type C = Omit<B, 'a'>; // OK
type C = Omit<B, 'a' | 'c'>; // Error: `c` not in `B`
type C = Omit<B, keyof A>; // Error
type C = Omit<B, A>; // OK: Exclude all intersections with A
type C = Omit<B, A & D>; // OK: Exclude all intersections
type C = Omit<B, D>; // Error: `D` has no any intersections with `B`, so `Omit` is unnecessary. |
PR is no longer relevant. |
No description provided.