I have the following oversimplified code snippet:
type a = 'a' | 'b';
const fn = <T extends a>(param: T): T => {
switch(param) {
case 'a':
return 'a' as T;
case 'b':
return 'b' as T;
}
};
I cannot figure out, why does the compiler complain about the lack of return, and is there a way to fix it that is future proof (e.g. not adding a default case, I wanna make sure all cases are explicitly handled, so if in the future the type is extended, I do want it to fail)
it works when I remove the generic, but in my scenario, the return type is generic on T
This is currently a missing feature of TypeScript, reported at microsoft/TypeScript#13215. Generics and narrowing don't work together very well; if you want to get exhaustiveness checking from
switch/casestatements, then you'll need the relevant value to be a union type directly and not a generic type constrained to a union. Maybe someday TypeScript will automatically apply exhaustiveness checks for generics, but for now it's not part of the language.For the example code as given, the simplest approach is to widen
paramfromTto"a" | "b"when doing the check:You could also use the
satisfiesoperator with a type assertion to safely widen a value without copying it to a new variable. That is,x satisfies Y as Ywill only compile ifxis assignable toY(thesatisfiescheck) and the whole expression will be treated as typeY(theasassertion):to provide context that
paramshould beThese approaches, of course, lose the generic behavior, so neither
"a"nor"b"will be seen as assignable to the generic type parameterT. That means you still need to use the type assertions inreturn "a" as Tandreturn "b" as T. There are often ways to refactor code to maintain the generic behavior, but they will necessarily not use aswitch/casestatement and are therefore out of scope for the question as asked.Playground link to code