Why I'm getting error indexing in a conditional when mapping an object type

92 views Asked by At

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?

1

There are 1 answers

0
jcalz On BEST ANSWER

This is a missing feature of TypeScript, requested at microsoft/TypeScript#48710. If you have a generic type X which is constrained to another type Y, then a conditional type like X extends Z ? T<X> : F<X> the type of X is re-constrained to Y & Z in the true branch T<X>, but it is not re-constrained to something like "Y but not Z" in the false branch.

In general it would not be correct to do so, since there are situations in which X extends Y is true, X extends Z is false, but X extends Y-but-not-Z is also false. For example:

type F<X extends { x: string | number }> =
    X extends { x: string } ? X['x']['toUpperCase'] : 
    X['x']['toFixed']; // <-- error!

That's flagged as an error for good reason; because you could evaluate something like:

type G = F<{ x: 0 | "a" }>

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:

type BBB = {
    [K in keyof Foo | "value"]:
      K extends "value" ? "4" : 
      K extends keyof Foo ? Foo[K] : never;
}

Playground link to code