Type narrowing for two variables #25492
-
Error on all return statements in the switch case, because type b is not narrowed along with a (here shown for the first case):
Is there a clean way to do this? The only solution I see is to have 9 type checks aka. a switch/if for |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
Support for narrowing the parent type based on a property is not well supported in TS, except for a few cases like The folks over at the TypeScript repository might have a more detailed answer on why that is. Related microsoft/TypeScript#42384 |
Beta Was this translation helpful? Give feedback.
-
@NitishKumar525 The Typescript compiler does not track dependencies between different variables in this way, so it doesn't "remember" that a.type and b.type are equal, for instance. This has been variously requested as a feature, but e.g. this issue shows the Typescript team don't currently consider it worth implementing. If it was my code, I would probably write it this way: switch (a.type) {
case "A": {
return b.type === "A" && a.propsOfA === b.propsOfA;
}
case "B": {
return b.type === "B" && a.propsOfB === b.propsOfB;
}
case "C": {
return b.type === "C" && a.propsOfC === b.propsOfC;
}
} That's three checks instead of nine. However, this might look inelegant since interface A {
type: "A";
propsOfA: string;
propsOfB?: never;
propsOfC?: never;
}
interface B {
type: "B";
propsOfA?: never;
propsOfB: number;
propsOfC?: never;
}
interface C {
type: "C";
propsOfA?: never;
propsOfB?: never;
propsOfC: boolean;
} With the types declared this way, your original code compiles with no errors. This is also actually a little safer than the original version, since it forbids objects like If the above is too much to write out manually, you can also construct these types programmatically: /**
* Makes a discriminated union type "safe" by requiring properties specific
* to some branches to be absent in other branches.
*/
type SafeUnion<T> = Simplify<T> extends infer _T ? _T extends unknown ? Replace<{[K in AnyKeyOf<T>]?: never}, _T> : never : never
/**
* Simplifies a type to make it more readable.
*/
type Simplify<T> = T extends unknown ? {[K in keyof T]: T[K]} : never
/**
* Similar to an intersection `S & T`, except shared properties take their
* types only from `T`.
*/
type Replace<S, T> = Simplify<T & Omit<S, keyof T>>
/**
* The keys of any member of a union type.
*/
type AnyKeyOf<T> = T extends unknown ? keyof T : never Then your code can be written like:
|
Beta Was this translation helpful? Give feedback.
@NitishKumar525 The Typescript compiler does not track dependencies between different variables in this way, so it doesn't "remember" that a.type and b.type are equal, for instance. This has been variously requested as a feature, but e.g. this issue shows the Typescript team don't currently consider it worth implementing.
If it was my code, I would probably write it this way:
That's three checks instead of nine. However, this migh…