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.
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
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
Fieldstype in your example is"propK" & "propL". For assignment purposes this type behaves asnever, 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: