Why isn't the constraint satisfied by this recursive map type in Typescript?

type CustomRecursiveMap<
    X extends Record<string, unknown>,
    Y extends Record<string, unknown>
> = {
    [M in keyof X]: M extends keyof Y
        ? X[M] extends Record<string, unknown>
            ? Y[M] extends Record<string, unknown>
                ? CustomRecursiveMap<X[M], Y[M]>
                : never
            : never
        : never
}

https://i.stack.imgur.com/r3UWB.png

playground

why X[M] does not comply with the constraint? Even after confirming it with

X[M] extends Record<string, unknown>
, how does this happen?

Answer №1

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Why does TypeScript include a question mark next to the argument when GraphQL specifies it as nullable true?

// image.entity.ts import { Field, ObjectType } from '@nestjs/graphql'; import { Column, DeleteDateColumn, Entity, PrimaryGeneratedColumn, } from 'typeorm'; @ObjectType() @Entity() export class ImageEntity { @PrimaryGenerate ...

What causes TypeScript to narrow the type when a return statement is present, but not when it is absent?

I am facing an issue with this script: type Input = string function util(input: Input) { return input } function main(input: Input | null) { const isNull = input === null if (isNull) { return 'empty string' } inpu ...

Mistakenly importing the incorrect version of Angular

While working on my Angular 1 app in typescript, I faced an issue when importing angular using the following syntax: import * as angular from 'angular'; Instead of importing angular from angular, it was being imported from angular-mocks. Thi ...

What is the process for incorporating personalized variables into the Material Ui Theme?

In the process of developing a react app with TypeScript and Material UI, I encountered an issue while attempting to define custom types for my themes. The error message I received is as follows: TS2322: Type '{ mode: "dark"; background: { default: s ...

Implementing Generic Redux Actions in Typescript with Iterable Types

Resolved: I made a mistake by trying to deconstruct an object in Object.assign instead of just passing the object. Thanks to the assistance from @Eldar and @Akxe, I was able to see my error in the comments. Issue: I'm facing a problem with creating a ...

When it comes to passing prop values through functions, TypeScript types do not provide suggestions

I'm struggling to find a way to ensure that developers have suggested types for specific props in my component, regardless of how they pass data to the prop. For example, when I directly pass an array of values to the prop: <Component someProp={[{ ...

React doesn't have file upload configured to update the state

I am working on integrating a file upload button that sends data to an API. To ensure only the button triggers the upload dialog and not the input field, I have set it up this way. Issue: The File is not being saved to state, preventing me from using a ...

What causes the discrepancy in results between these two NodeJS/Typescript imports?

Within my NodeJS project, I have integrated typescript version 3.2 alongside express version 4.16 and @types/express version 4.16. My development is focused on using Typescript with the intention of transpiling it later on. The guidelines for @types/expre ...

Enhancing native JavaScript types in TypeScript 1.8 with the power of global augmentation

Currently, I am working on expanding the capabilities of native JavaScript types using the new global augmentation feature in TypeScript 1.8, as detailed in this resource. However, I'm encountering difficulties when the extension functions return the ...

Exploring the Concepts of Union and Intersection Types in Typescript

I am trying to wrap my head around Union and Intersection types in TypeScript, and I've come across a case that's puzzling me. You can check it out on this Playground Link interface A { a: number; } interface B{ b: boolean; } type Un ...

Issue with VueJS 2 and TypeScript: computed value unable to recognize property specified in data object

When creating the following component: <template lang="html"> <div> <p>{{ bar }}</p> </div> </template> <script lang="ts"> import Vue from 'vue'; export const FooBar = Vue.ex ...

Change an array of objects into a map where each object is indexed by a unique key

I'm attempting to transform an array of objects into a map, with the index based on a specific attribute value of the object in typescript 4.1.5 Additionally, I am only interested in attributes of a certain type (in this case, string) A similar ques ...

Issue with loading React Router custom props array but custom string works fine

I am facing an issue with my ReactTS-App where I pass a prop via Router-Dom-Props to another component. The problem arises when I try to use meal.food along with meal.name, or just meal.food alone - it doesn't work as expected. Uncaught TypeError: mea ...

Discovering the file system with window.resolveLocalFileSystemURL in Typescript for Ionic 3

After reviewing the documentation found on this link for the File plugin, I came across a paragraph that discusses how to add data to a log file. See the example code below: window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function (dirEntry) ...

An issue has occurred: changes.forEach does not function as expected

Encountered an issue while attempting to retrieve data from Firestore using Angular/Ionic. PizzaProvider.ts getAllPizzas() { return this._afs.collection<Pizzas>('pizzas', ref => ref); } pizzas-list.ts pizzas: Observable<any[]& ...

When trying to pass 3 parameters from an Angular frontend to a C# MVC backend, I noticed that the server side was receiving null

I have encountered an issue where I am attempting to pass 3 parameters (2 types and one string) but they are showing up as null on the server side. Below is my service: const httpOptions = { headers: new HttpHeaders({ 'Content-Type&ap ...

What is the significance of `new?()` in TypeScript?

Here is a snippet of code I'm working with in the TypeScript playground: interface IFoo { new?(): string; } class Foo implements IFoo { new() { return 'sss'; } } I noticed that I have to include "?" in the interface met ...

What could be causing the error when my file is running?

Whenever I attempt to run a file using the command node database.ts, an error pops up. Can someone help me identify what's wrong with my syntax? This is how the file appears: import { Sequelize } from 'sequelize-typescript'; export const ...

What is the method for including a dynamic image within the 'startAdornment' of MUI's Autocomplete component?

I'm currently utilizing MUI's autocomplete component to showcase some of my objects as recommendations. Everything is functioning correctly, however, I am attempting to include an avatar as a start adornment within the textfield (inside renderInp ...

Angular application experiencing loading issues on Firefox caused by CSP problems

I am encountering an issue while trying to access my app on the testing server. The internal URL I am using is: . However, when I visit the page, it appears completely blank and upon inspecting the dev console, I see the following error message. This situa ...