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 type doesn't work for deconstructor with rest #73

Closed
Akuukis opened this issue Apr 12, 2019 · 6 comments · Fixed by #78
Closed

Omit type doesn't work for deconstructor with rest #73

Akuukis opened this issue Apr 12, 2019 · 6 comments · Fixed by #78

Comments

@Akuukis
Copy link

Akuukis commented Apr 12, 2019

Please see the minimal reproducible example below and questions inline.

type TGeneric<T extends string = string> = Extract<
    | {pet: 'dog', age: number}
    | {pet: 'cat', age: number}
,
    {pet: T, age: number}
>

const someScope = <T extends 'dog' | 'cat' = 'dog'>(arg1: TGeneric<T>) => {
    const {age, ...rest} = arg1

    let A: Omit<TGeneric<T>, 'age'> = rest
    //  ^ I expect this to work, but it complains, why?

    let B: Pick<TGeneric<T>, SetDifference<keyof TGeneric<T>, 'age'>> = rest
    //  ^ From Error message above I figured out a type that doesn't complain. 
    //    What's the difference? Is this expected behavior?
}
@piotrwitek
Copy link
Owner

Omit is wrapped in conditional type and it's distributive so this works:

// @dts-jest:pass:snap -> Pick<Props, "name" | "visible"> | Pick<NewProps, "other">
Omit<Props | NewProps, 'age'>

Historically it was introduced by some user request, I was not really sure about this change but now I think it's actually more harmful than useful.

I might bring back previous behavior which was non-distributive.

What do you think?

@Akuukis
Copy link
Author

Akuukis commented Apr 12, 2019

I dig deeper and found another difference. I think that first scenario looks best if it worked.

import { Omit, SetDifference } from 'utility-types'

type TGeneric<T extends string = string> = Extract<
    | {age: number, pet: 'dog'}
    | {age: number, pet: 'cat'}
,
    {age: number, pet: T}
>

class Example<T extends 'dog' | 'cat' = 'dog' | 'cat'> {

    public caseA: Omit<TGeneric<T>, 'age'>
    public caseB: Pick<TGeneric<T>, SetDifference<keyof TGeneric<T>, 'age'>>

    public constructor(arg1: TGeneric<T>) {

        const {age, ...rest} = arg1

        this.caseA = rest
        // Scenario #1: I expect this to typecheck, but it complains, why?

        this.caseB = rest
        // Scenario #2: From Error message above I figured out a type that
        // doesn't complain. What's the difference? Is this expected behavior?

        this.caseA = rest as Omit<TGeneric<T>, 'age'>
        // Scenario #3: At least this still works, but missed the type safety somewhat.

    }

}

const lazyTypeGuard = (example: Example): example is Example<'cat'> => true
// I expect this to typecheck, but it errors. Why?
// Comments out `public caseB:` and it will typecheck.
// Therefore, scenario #3 is the only way that can typecheck the whole snippet.

@Akuukis
Copy link
Author

Akuukis commented Apr 12, 2019

Typescript has in-build Omit in dev branch. I just checked that with Omit from 3.5.0-dev.20190412 second snippet doesn't typechecks neither commenting out caseA nor caseB. So that's worse than now.

Perhaps lets bring the conversation to them about upcoming in-built Omit?

@piotrwitek
Copy link
Owner

piotrwitek commented Apr 25, 2019

@Akuukis Now it struck me that it might be related to the first example, no? :microsoft/TypeScript#28884

Sorry but the second example is unreadable to me, you should try to reduce and simplify it to a minimum possible. (First was good)

@piotrwitek
Copy link
Owner

piotrwitek commented Apr 25, 2019

Here is the simplification of your issue:

const someScope = <T extends { age: number }>(pet: T) => {
  const { age, ...rest } = pet;

  const A: Omit<T, 'age'> = rest;
  // Type 'Pick<T, Exclude<keyof T, "age">>' is not assignable to type 'Omit<T, "age">'.ts(2322)
};

And changing Omit to below fixes the issue:

export type Omit<T extends object, K extends keyof T> =  Pick<T, SetDifference<keyof T, K>>

@piotrwitek
Copy link
Owner

piotrwitek commented Apr 27, 2019

Also the new Omit is now the same implementation as build-in Omit coming in TypeScript v3.5 to be future compatible

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants