I'm adding a new property value:"4" to the object Foo, I have two approches in aaa and bbb. When hovering over them, both show the same expected result:{ name:string; value:"4";}
but there is an error in type bbb saying: Type 'k' cannot be used to index type 'Foo'
type Foo={
name:string
}
type aaa={
[k in keyof Foo|"value"]:k extends keyof Foo?Foo[k]:"4"
}
type bbb={
[k in keyof Foo|"value"]:k extends "value"?"4":Foo[k]
^^^^^^
}
Why the error? Is it that the conditional in aaa being true constrains k so that Foo[k] become valid?
This is a missing feature of TypeScript, requested at microsoft/TypeScript#48710. If you have a generic type
Xwhich is constrained to another typeY, then a conditional type likeX extends Z ? T<X> : F<X>the type ofXis re-constrained toY & Zin the true branchT<X>, but it is not re-constrained to something like "Ybut notZ" in the false branch.In general it would not be correct to do so, since there are situations in which
X extends Yis true,X extends Zis false, butX extends Y-but-not-Zis also false. For example:That's flagged as an error for good reason; because you could evaluate something like:
where
{x: 0 | "a"} extends {x: string | number}is true, and{x: 0 | "a"} extends {x: string}is false, but{x: 0 | "a"} extends {x: number}is also false.So it's not considered a bug to fail to re-constrain the false branch, because it's not always warranted.
Still, in situations like this where the generic type is constrained to a union of literal types and you are checking it against some subset of that union, and the generic type is only ever going to be a single member of that union (e.g., it's a distributive conditional type or you are iterating over a set of known keys in a mapped type), then it would be safe to do it. It just hasn't been implemented. So it's a missing feature.
For now the workarounds are to reorder your conditional type as you've shown above, or do a possibly redundant second check:
Playground link to code