The reason for this issue is a bug in TypeScript, which can be found at microsoft/TypeScript#45651. It seems that in versions TS4.5 and earlier, performing a conditional type check on an indexed access types may not always properly constrain the type in the true branch. Fortunately, this bug has been resolved in microsoft/TypeScript#47791, which will be included in the upcoming TS4.6 release. Users can already use the release candidate version labeled as 4.6.1-rc
:
type RecursiveMap<
A extends Record<string, unknown>,
B extends Record<string, unknown>
> = {
[K in keyof A]: K extends keyof B
? A[K] extends Record<string, unknown>
? B[K] extends Record<string, unknown>
? RecursiveMap<A[K], B[K]> // okay
: never
: never
: never
}
Playground link to 4.6.1-rc
If users cannot wait for the fix, they can apply a workaround using the Extract<T, U>
utility type in an alternative manner. By substituting U
with Extract<T, U>
, where type T
can be assigned to type
U</code even if the compiler doesn't recognize it correctly, the issue can be corrected. As long as the assumptions about <code>T
and
U
are accurate, later evaluation of
Extract<T, U>
will result in
T
(however, incorrect assumptions could lead to something closer to
T & U
, potentially resulting in
never
, so caution is advised):
type RecursiveMap<
A extends Record<string, unknown>,
B extends Record<string, unknown>
> = {
[K in keyof A]: K extends keyof B
? A[K] extends Record<string, unknown>
? B[K] extends Record<string, unknown>
? RecursiveMap<
Extract<A[K], Record<string, unknown>>,
Extract<B[K], Record<string, unknown>>
>
: never
: never
: never
}
Playground link to code