A simple example makes the question more clear
type T1 = {
asdf: string
type: "T1"
}
type T2 = {
qwer: string
type: "T2"
}
// discriminated on "type"
type AnyT = T1 | T2
// either of the literal discriminant values
type AnyDiscriminantOfT = "T1" | "T2"
//map the discriminant to a different type
type MappedTest<T extends AnyDiscriminantOfT> = T extends "T1" ? "foo" : T extends "T2" ? "bar" : never
//function signature works as expected, but body won't compile despite being correct
const testFn = <T extends AnyT>(arg: T): MappedTest<T["type"]> => {
if (arg.type === "T1") {
//Type '"foo"' is not assignable to type 'MappedTest<T["type"]>'
return "foo"
} else if (arg.type === "T2") {
//Type '"bar"' is not assignable to type 'MappedTest<T["type"]>'.
return "bar"
} else {
throw new Error("invalid arg")
}
}
const val: T1 = {asdf: "123", type: "T1"}
//fn signature is correct, typeof res is "foo"
const res = testFn(val)
I don't understand why this doesn't compile. TS narrows the type of arg within the conditional and the values being returned are correct yet TS still reports an error.
It seems that control flow analysis does not apply when TS is checking return types involving generics?
The type signature of the function is valid and correct but I can't think of a way to write the function body that compiles in TS without resorting to any. The other option I suppose is to use overloads instead of a mapped return type, but those can come with other surprises.
Is there any way to write this function body in TS or have I simply hit a corner case of the language?