Typescript: keyof with union types generates wrong list of keys.

Created on 11 Dec 2018  路  3Comments  路  Source: microsoft/TypeScript

TypeScript Version: 3.3.0-dev.20181211

keyof union

Code

interface A {
    propA: string;
}

interface K {
    propK: string;
}

interface L {
    propL: string;
}

type CombinedOr = K | L;

type CombinedAnd = A & CombinedOr;

type Fields = keyof CombinedAnd;

const foo: Fields = "propK";

Expected behavior:
"propK" is a valid assignment to foo: Fields. Since CombinedOr can hold K or L I expect the compiler to generate multiple correct assignments to foo. The first option would be to hold propA or propK and the second option would be to hold propA or propL.

Actual behavior:
error TS2322: Type '"propK"' is not assignable to type '"propA"'.

Playground Link: http://www.typescriptlang.org/play/#src=interface%20A%20%7B%0D%0A%20%20%20%20propA%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20B%20%7B%0D%0A%20%20%20%20propB%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20C%20%7B%0D%0A%20%20%20%20propC%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20K%20%7B%0D%0A%20%20%20%20propK%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20L%20%7B%0D%0A%20%20%20%20propL%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20M%20%7B%0D%0A%20%20%20%20propM%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Atype%20CombinedOr%20%3D%20K%20%7C%20L%20%7C%20M%3B%0D%0A%0D%0Atype%20CombinedAnd%20%3D%20A%20%26%20B%20%26%20C%20%26%20CombinedOr%3B%0D%0A%0D%0Atype%20Fields%20%3D%20keyof%20CombinedAnd%3B%0D%0A%0D%0Aconst%20foo%3A%20Fields%20%3D%20%22propK%22%3B

Related Issues:
I don't know if this already exists. I haven't found any.

Working as Intended

Most helpful comment

@vcarl What you're seeing there is that we defer elimination of vacuous intersection types until they are put into a union type. The Fields type in your example is "propK" & "propL". For assignment purposes this type behaves as never, but we keep it around to allow you to better diagnose where the never-like type came from. However, the minute you union it with something we remove it. For example:

type X = "propK" & "propL";
type Y = X | string;  // Reduced to just string

All 3 comments

This is working as intended. When applied to a union type, the keyof type operator produces a union of the keys that are present in _all_ of the constituents of the union. In other words, a key type that is safe to apply to every possible shape of the union. In your example, only "propA" is known to be a valid key for CombinedAnd.

Somebody provided this link to a talk you gave where you discuss exactly this, so cheers. I wonder though if this slightly different demo should result in a never type, though? If there are no keys in common, it seems to produce a type that can't be satisfied.

@vcarl What you're seeing there is that we defer elimination of vacuous intersection types until they are put into a union type. The Fields type in your example is "propK" & "propL". For assignment purposes this type behaves as never, but we keep it around to allow you to better diagnose where the never-like type came from. However, the minute you union it with something we remove it. For example:

type X = "propK" & "propL";
type Y = X | string;  // Reduced to just string
Was this page helpful?
0 / 5 - 0 ratings

Related issues

weswigham picture weswigham  路  3Comments

wmaurer picture wmaurer  路  3Comments

jbondc picture jbondc  路  3Comments

Zlatkovsky picture Zlatkovsky  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments