Home > Mobile >  Typescript Why this recursive map type does not satisfy the constraint?
Typescript Why this recursive map type does not satisfy the constraint?

Time:02-28

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]>
                : never
            : never
        : never
}

enter image description here

playground

why A[K] does not satisfy the constraint? I have already checked it with A[K] extends Record<string, unknown>, how is this make sense?

CodePudding user response:

This is due to a bug in TypeScript, see microsoft/TypeScript#45651. Apparently in TS4.5 and below, this kind of conditional type check on an indexed access types doesn't always properly constrain the type in the true branch. Luckily this bug was fixed in microsoft/TypeScript#47791 which should be part of the upcoming TS4.6 release, the release candidate version of which you can use right now 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 you can't wait for the fix version, you can work around it with an off-label use of the Extract<T, U> utility type. If you have a type T that you know is assignable to a type U but the compiler doesn't, you can replace U with Extract<T, U> and fix the problem. As long as you are correct about T and U, then later Extract<T, U> will evaluate to T (if you're wrong then it will evaluate so something closer to T & U which may be never so be careful):

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

  • Related