Skip to content
This repository has been archived by the owner on Feb 16, 2021. It is now read-only.

Use keyof object in Omit for better type checking #42

Closed
wants to merge 2 commits into from
Closed

Use keyof object in Omit for better type checking #42

wants to merge 2 commits into from

Conversation

vlazh
Copy link

@vlazh vlazh commented Dec 29, 2018

No description provided.

@gcanti
Copy link
Owner

gcanti commented Dec 29, 2018

@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>

for better type checking

what's the use case which led to this change?

@vlazh
Copy link
Author

vlazh commented Dec 29, 2018

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 A doesn't contain a and any other intersections? In this case Omit is totally not needed and compiler can't tell about it.
May be need other type like OmitKeys or ExcludeKeys?

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 Omit?

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'>;

@vlazh
Copy link
Author

vlazh commented Dec 29, 2018

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.  

@vlazh
Copy link
Author

vlazh commented Dec 29, 2018

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.  

@vlazh
Copy link
Author

vlazh commented Dec 29, 2018

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.  

@raveclassic
Copy link

@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?

@vlazh
Copy link
Author

vlazh commented Jan 5, 2019

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?

@raveclassic
Copy link

So I guess it's more about developer experience/polishing the code than fixing some type-related issues, right?
Anyway, I don't have any strong opinion about this issue so it's up to @gcanti to decide, just wanted to ask.

@vlazh
Copy link
Author

vlazh commented Jan 5, 2019

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.

@raveclassic
Copy link

raveclassic commented Jan 5, 2019

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 Omit heavily in our projects and this improvement will end up in a massive breaking change for us.

@vlazh
Copy link
Author

vlazh commented Jan 6, 2019

Yeah, it might be problems for many code bases. So perhaps I should use the general implementation.

@vlazh
Copy link
Author

vlazh commented Jan 6, 2019

It is all I have been able to think for less pain of using suggested Omit implementation in existing codebase. Fortunately compiler will be able to show an mistakes. Autocomplete of keys also supports.

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.  

@vlazh
Copy link
Author

vlazh commented Jan 8, 2019

PR is no longer relevant.

@vlazh vlazh closed this Jan 8, 2019
@vlazh vlazh deleted the patch-1 branch January 8, 2019 06:59
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants