Typescript: Error in Diff / Omit types

Created on 11 Jan 2018  路  5Comments  路  Source: microsoft/TypeScript

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 https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 so are present in a few DefinitelyTyped packages.
There was no error in [email protected]. Error discovered in recompose (among others) on DefinitelyTyped.

Bug Fixed

Most helpful comment

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

All 5 comments

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.

Fix is up at #21156

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

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

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;...'
Was this page helpful?
0 / 5 - 0 ratings