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

Error in Diff / Omit types #21148

Closed
ghost opened this issue Jan 11, 2018 · 5 comments
Closed

Error in Diff / Omit types #21148

ghost opened this issue Jan 11, 2018 · 5 comments
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@ghost
Copy link

ghost commented Jan 11, 2018

TypeScript Version: 2.7.0-dev.20180111

Code

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;

Expected behavior:

No error.

Actual behavior:

src/a.ts(3,43): error TS2344: Type '({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[keyof T]' does not satisfy the constraint 'keyof T'.
  Type '({ [P in T]: P; } & { [P in U]: never; })[keyof T]' is not assignable to type 'keyof T'.

These types were taken from #12215 (comment) so are present in a few DefinitelyTyped packages.
There was no error in typescript@2.7.0-dev.20180110. Error discovered in recompose (among others) on DefinitelyTyped.

@ghost ghost added the Bug A bug in TypeScript label Jan 11, 2018
@mhegazy mhegazy added this to the TypeScript 2.7.1 milestone Jan 11, 2018
@sandersn
Copy link
Member

Briefly, this error is correct. The second half of Diff is not sound:

declare function f<T,K extends keyof T>(sub: ({ [P in K]: never })[keyof T]): void;

Consider T={ a, b }, K='a'. Then { [P in K]: any }[keyof T] is not defined when keyof T='b'.

The question is how to make these libraries work again.

@sandersn
Copy link
Member

Fix is up at #21156

@Jessidhia
Copy link

@sandersn I believe that the part of Diff you said is not sound is what makes it actually work as a difference type hack. I don't quite understand the trick myself, but it happens to work and is very useful 🤔

@sandersn
Copy link
Member

@DanielRosenwasser also pointed out that taken as a whole, ({ [P in T]: P } & { [P in U]: never})[T] is sound, because the intersection is gauranteed to have at least the properties of T. However, the current algorithm just iterates through the members of the intersection for assignability.

In any case, my initial diagnosis is wrong. The reason these types fail is that the constraint of ({ [P in T]: P } & { [P in K]: never} & { [s:string]: never })[keyof T] must be assignable to keyof T in order to satisfy K extends keyof T in the second parameter of Omit. Before #17912 the constraint was incorrectly any, which is of course assignable to anything. After #17912, the constraint was incorrectly not present, because of a transform that removes string-index-only types from intersections.

The fix in #21156 avoids this transform, so the constraint is never, which is assignable to keyof T.

@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Jan 13, 2018
weltenwort added a commit to weltenwort/eui that referenced this issue Jan 19, 2018
The types in EuiContextMenu caused errors that seemed to result from
usage of the `Omit<>` helper. It might be fixed with
microsoft/TypeScript#21148, which is expected
to be included in 2.7.1.
@codeaid
Copy link

codeaid commented Mar 11, 2018

Has this been released already because Omit doesn't seem to work in 2.7.2 with optional properties.

I have a base interface that comes from a library, which defines lots of optional component properties and my component is expected to allow defining all those properties except a couple. I was trying to use the oh so popular implementation of Omit but it seems to be completely ignoring optional types.

Consider this scenario:

type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;

export interface DropdownProps {
    optional1?: string;
    optional2?: string;
    optional3?: string;
}

export interface MyComponentProps extends Omit<DropdownProps, 'optional2'> {
    custom1?: string;
}

Then when I use my component that implements the interface as follows - class MyComponent extends React.Component<MyComponentProps> {} - I am not being allowed to define any of the original component properties apart from the custom1:

Property 'optional1' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComponent> & Readonly<{ children?: ReactNode;...'

@microsoft microsoft locked and limited conversation to collaborators Jul 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests

4 participants