I have this base case:
type Ids = "foo" | "bar" | "bazz";
interface IFoo {
foo: string;
}
interface IBar {
bar: number;
}
interface IBazz {
bazz: boolean;
}
type Ret<T extends Ids> = T extends "foo" ? IFoo :
T extends "bar" ? IBar :
T extends "bazz" ? IBazz :
never;
function a<T extends Ids>(id: T): Ret<T> {
switch (id) {
case "bar":
return { bar: 1 };
case "foo":
return { foo: "foo" };
case "bazz":
return { bazz: true };
}
}
const bar: IBar = a("bar");
const foo: IFoo = a("foo");
const bazz: IBazz = a("bazz");
As you can see, Typescript is not satisfied with my a function implementation. What should I change to compile this function but still keep guarantees in the last three statements?
It's an open issue in TypeScript (see microsoft/TypeScript#33912) that the compiler is generally unable to verify that a particular function return value conforms to a conditional type that depends on an as-yet-unspecified generic type parameter, like
Ret<T>inside the implementation ofa()whereTis unresolved. This is related to the fact that TypeScript is unable to narrow type parameters via control flow analaysis] (see microsoft/TypeScript#24085), so checkingidwith aswitch/casestatement might narrow the type ofidto, say,"bar", but it does not narrow the type parameterTto"bar", and thus it can't guarantee thatRet<"bar">is an acceptable output.One thing you can do is accept that the compiler can't verify this for you and use type assertions or an overload to loosen the implementation typing enough to avoid the errors. This will work, but the compiler is not guaranteeing type safety. For example, with an overload:
now there's no error, and there's some type safety... you can't return a completely incorrect type like
{spazz: true}, but you can swap thecases around and it won't notice:So you have to be careful.
Another solution to this particular case is to abandon conditional types in favor of generic indexing, like this:
The compiler is able to verify that what we are doing here is safe, because we're indexing into an object of type
RetMapwith theidkey of typeK. Oh, and if you are unhappy that this version preemptively calculates return values it won't use, you could refactor to use getters, which the compiler is also happy with:Okay, hope that helps you proceed; good luck!
Playground link to code