What is the reason for TypeScript's decision to lazily evaluate constrained class generics?

I am experiencing confusion with the TypeScript declaration provided below.

class C<T extends {}> {
    method() {
        type X = T extends {} ? true : false;
        //   ^? type X = T extends {} ? true : false;
        // Why is X not `true`?
    }
}

Playground

It appears that T is being treated as an unknown entity, making it difficult to obtain any useful type information from it.

Even attempting to evaluate property types does not produce the expected results:

class C<T extends { foo: number }> {
    method() {
        type X = T['foo'];
        //   ^? type X = T['foo'];
        // Why is X not `number`?
    }
}

Interestingly, the types are accurately assessed when assigning values to them:

const a: X = 5 // works correctly
const b: X = "str" // fails correctly

Can anyone clarify what is happening here?

Answer №1

Conditional types that rely on a generic type parameter T can exhibit intricate behaviors. Even seemingly simple expressions like T extends AAA ? BBB : CCC can produce fascinating outcomes due to the concept of distribution over unions in T. Currently, the compiler postpones evaluation of such types until generic type arguments are specified, at which point the type ceases to be generic. The compiler treats deferred generic conditional types as essentially "opaque," making it challenging for the compiler to determine what values can or cannot be assigned to them.

It would be convenient if the compiler could promptly resolve generic conditional types when there is sufficient information about the generic type parameter to do so, but this is not the case. When asked why, the answer lies in the complexity of achieving a resolution method that is both effective and does not significantly impact compiler performance.

This issue has been raised numerous times on GitHub, and within these discussions resides the closest thing to an official explanation for the current behavior:

  • microsoft/TypeScript#52144 "Resolve deferred conditional types to their true branch when instantiated with a type parameter constrained to the tested type". This feature request remains open. According to this comment:

    The reason for deferral is that while resolving to the true branch may be safe, doing so for a generic type might lead to issues if the type parameter is instantiated with a more specific type that should go to the true branch.

    We don't yet have a concept of a "partial deferral"; experimenting with this idea would be quite challenging.

  • microsoft/TypeScript#48243 "Conditional type doesn't go to true or false branch." Although marked as 'Working as Intended', the issue has been closed. As per this comment:

    Conditional types do not always follow a linear path (meaning they exhibit predictable behavior between T and a subtype of T), and determining this requires complex reasoning involving scenarios where types behave differently than expected.

There may be additional discussions on this topic, but the explanations above provide insight into the rationale behind the existing behavior.


Regarding the aspect of indexed accesses in generics, the reasoning for deferring stems from the fact that almost any type can have subtypes, including number (which includes numeric literal types like 0.5 and 42). Therefore, if T extends {foo: number}, its foo property may be more specific than just a generic number type, and prematurely resolving T["foo"] to number could result in loss of essential information.

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

What are the distinctions between using getStaticPaths + getStaticProps and useRouter in NextJS?

I'm currently diving into the world of NextJS and finding myself puzzled by the distinctions between getStaticProps & getStaticPaths compared to utilizing useRouter().query. At this point, it appears to me that both methods serve a similar purpos ...

Issue: Unable to resolve all parameters for LoginComponent while implementing Nebular OAuth2Description: An error has occurred when attempting to

I have been attempting to utilize nebular oauth in my login component, following the documentation closely. The only difference is that I am extending the nebular login component. However, when implementing this code, I encounter an error. export class Lo ...

Angular does not propagate validation to custom form control ng-select

In my Angular 9 application, I am utilizing Reactive Forms with a Custom Form Control. I have enclosed my ng-select control within the Custom Form Control. However, I am facing an issue with validation. Even though I have set the formControl to be requir ...

Tips for effectively utilizing Mongoose models within Next.js

Currently, I am in the process of developing a Next.js application using TypeScript and MongoDB/Mongoose. Lately, I encountered an issue related to Mongoose models where they were attempting to overwrite the Model every time it was utilized. Here is the c ...

Is it possible that a declaration file for module 'material-ui/styles/MuiThemeProvider' is missing?

I have been trying to implement the react material-ui theme after installing it via npm. However, I am encountering errors when adding 'import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";' in boot-client.tsx: TS7016: Could not ...

Dealing with Error TS2769 in Visual Studio Code when passing props to a custom component in Vue 2 with Typescript

I've encountered an issue with a Vue JS component that involves passing a custom prop. I am utilizing the Vue Options API without utilizing the class component syntax. Whenever I pass ANY prop to my custom component the-header, I receive an error sta ...

Using TypeScript to create a state object in Koa

I am currently utilizing Koa () as the framework for my backend, which consists of Node.js + TypeScript. Koa permits and recommends using the built-in `ctx.state` to store and pass data between different middleware functions. I have been adhering to this ...

TypeScript's type inference feature functions well in scenario one but encounters an error in a different situation

I recently tried out TypeScript's type inference feature, where we don't specify variable types like number, string, or boolean and let TypeScript figure it out during initialization or assignment. However, I encountered some confusion in its be ...

Can a new class be created by inheriting from an existing class while also adding a decorator to each field within the class?

In the following code snippet, I am showcasing a class that needs validation. My goal is to create a new class where each field has the @IsOptional() decorator applied. export class CreateCompanyDto { @Length(2, 150) name: string; @IsOptional( ...

Having an issue with displaying the country name and country code in a table using the Angular7 custom pipe

country code: "ab", "aa", "fr", ... I need to create a custom pipe that will convert a countryCode into a countryName, such as: "ab" → "Abkhazian", "ch" → "Chinese", "fr" ...

What sets apart the Partial and Optional operators in Typescript?

interface I1 { x: number; y: string; } interface I2 { x?: number; y?: string; } const tmp1: Partial<I1> = {}, tmp2: I2 = {}; Can you spot a clear distinction between these two entities, as demonstrated in the above code snippet? ...

The resolver function in the Nextjs higher order API is not defined

I am trying to create a custom wrapper function for my NextJs API routes that will verify a JWT in the request, validate it, and then execute the original API handler. Here is how I have defined my wrapper function: interface ApiError { message: string, ...

Incorporating an alternate object array to update an array of objects: A

There are two object arrays, the main array and the temp array. The goal is to compare the main array with the temp array and update the values in the main array based on matching IDs. In this example, IDs 2 and 3 match in both arrays. Therefore, the valu ...

An effective way to extract targeted information from an array of objects using Typescript

I'm having trouble extracting the IDs from the selected items in my PrimeNG DataTable. Despite searching on Google, I couldn't find much information about the error I'm encountering... ERROR in C:/Users/*****/Documents/Octopus/Octopus 2.0/s ...

Tips for finalizing a subscriber after a for loop finishes?

When you send a GET request to , you will receive the repositories owned by the user benawad. However, GitHub limits the number of repositories returned to 30. The user benawad currently has 246 repositories as of today (14/08/2021). In order to workarou ...

Is it possible to design a Typescript type that only contains one property from a defined set and is indexable by that set as well?

I have the different types listed below: type OrBranch = { or: Branch[] } type AndBranch = { and: Branch[] } I need a type called Branch that can either be an OrBranch or an AndBranch. I initially attempted this: type Branch = AndBrand | OrBranch ...

Sign up for a feature that provides an observable exclusively within an if statement

There is an if clause in my code that checks for the presence of the cordova object in the window global object. If cordova is present, it will make a http request and return the default angular 2 http observable. If the application is in a web context wh ...

What is the best way to send multiple data using GetServerSideProps?

I have a challenge where I need to pass multiple sets of sanity data into a component, but I am restricted to using getServerSideProps only once. How can I work around this limitation to include more than one set of sanity data? pages > members.tsx exp ...

Passing data from ModalService to a component

Currently, I am attempting to utilize the ngx-bootstrap-modal in order to transfer data from a modal service to a modal component. While reviewing the examples, it is suggested to use the following code: this.modalService.show(ModalContentComponent, {init ...

Guide for launching Electron on a local host server during development and for production builds

I have a project using Next.js + Electron + Typescript. I used the npx create-next-app --example with-electron-typescript command to generate the initial code. When I run npm run dev (which actually runs npm run build-electron && electron . ), the ...