Unfortunately, the compiler struggles to interpret the logic behind the line memo[key] = config[key]
when key
is a union type such as keyof AB
. It fails to analyze the line based on the identities of the variables; instead, it solely focuses on their types. However, the validity of memo[key] = config[key]
can be assured because the same key
is used on both sides. If we introduced key1
and key2
, both as type keyof AB
, then the compiler's analysis would make more sense.
This level of analysis has been implemented in microsoft/TypeScript#30769. By executing memo[key1] = config[key2]
, the compiler recognizes that accessing config[key2]
where key2
is a union results in another union, string | number
. Yet, assigning to memo[key1]
could potentially be unsafe. The only scenario for this assignment to be safe is if config[key2]
could be assigned to
memo[key1]</code regardless of the specific value of <code>key1
. This implies that
config[key2]
must simultaneously be a
string
and a
number
: essentially an
intersection of
string & number
. However, since there is no valid value for such a type, the compiler reduces this intersection to
the impossible never
type. Therefore, the compiler correctly marks
memo[key1] = config[key2]
as an error due to uncertainty about the specific value of
key1</code for a secure assignment. These analytical measures are valuable in detecting genuine errors.</p>
<p>With just <code>key
existing rather than
key1
and
key2
, the compiler errs on the side of caution considering the unlikely event of assigning a
string
to a
number
or vice versa.
So, what is the solution? According to a comment from the developer of ms/TS#30769, ensuring TypeScript adheres to the intended logic involves defining the type of key
as a generic type K
constrained to a union instead of being a union itself. Essentially, you can make the reduce()
callback generic as demonstrated below:
function test(config: AB) {
getObjectKeys(config).reduce(<K extends keyof AB>(memo: AB, key: K) => {
memo[key] = config[key];
return memo;
}, {} as AB);
}
This method works because both sides of the assignment are perceived as the generic type AB[K]
. Interestingly, this approach poses similar risks to using a union (e.g., assigning key1
and key2
of type K
, with K
representing the full union keyof AB
).indexed access types.
Playground link to code