Discovering the generic parameter types with union in TypescriptUncover the

I've been struggling with the code snippets below for a while. Can someone explain why e4 is defined as string and not String?

type PropConstructor4<T = any> = { new(...args: any[]): (T & object) } | { (): T }
type e4 = StringConstructor extends PropConstructor4<infer R> ? R : false // why string not String ???

I conducted some tests below and feel like I'm starting to understand.

type a4 = StringConstructor extends { new(...args: any[]): (infer R & object) } ? R : false // String
type b4 = StringConstructor extends { (): ( String) } ? true : false // true
type c4 = StringConstructor extends { (): (infer R) } ? R : false // string

Moreover, I am puzzled as to why e5 is String and not string?

type PropConstructor5<T = any> = { new(...args: any[]): (T ) } | { (): T }
type e5 = StringConstructor extends PropConstructor5<infer R> ? R : false //why String not string??

Answer №1

TL;DR the compiler uses heuristics to prioritize different inference sites, ultimately inferring the type from the site with the highest priority.


In general, TypeScript determines a specific type for a type parameter (denoted as R in all examples) by analyzing each inference site, where the type parameter appears in the expression being matched. For instance, in

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//         ^^^^^^^  <-- inference sites -->   ^^^^^^^

there are two inference sites for the type parameter R. The goal of the compiler is to reconcile StringConstructor with the entire expression by examining an inference site, proposing a potential specific type for that site, and then evaluating the full expression for compatibility with the proposed type.

Let's consider the example above with type P and speculate on the outcomes when different inference sites are inspected.


If the compiler selects the first inference site for inspection:

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//         ^^^^^^^  <-- inspect this

In this scenario, the inferred candidate would be string, since String("hello") results in a string output. Subsequently, it confirms if string satisfies the complete expression. Since StringConstructor indeed extends

(() => string) | { new(...args: any[]): (string & object) }
by extending the initial union member, R is deduced as string if only the first inference site is considered.


What about the second inference site?

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//                           inspect this --> ~~~~~~~~

In this case, the derived candidate would be String, given that new String("hello") produces a String result. It then verifies if String aligns with the entire expression. As StringConstructor does extend

(() => String) | { new(...args: any[]): (String & object) }
by extending both sides of the union, R is inferred as
String</code if the compiler isolates the second inference site.</p>
<hr />
<p>It may also be plausible for the compiler to contemplate <em>both</em> inference sites simultaneously, merging them based on variance into a union/supertype or intersection/subtype of the candidates. In this context, as the parameters are covariant, I'd speculate that <code>string | String
or simply String (being a supertype of string) could be potential outcomes.


Consequently, one might envisage string, String, or string | String as feasible results for the aforementioned expression. But what actually transpires?

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
// string

The outcome is string. This indicates that the compiler accords prioritization to the first inference site. A contrasting scenario unfolds in the subsequent example:

type O = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R) } ? R : never
// String

Here, the compiler favors the second inference site instead. Evidently, (infer R & object) holds lower precedence compared to just infer R.


How does the compiler allocate priorities to diverse inference sites? Regrettably, I lack comprehensive insight into this aspect.

Historically outlined in the TypeScript Specification document, which is now archived due to obsolescence, details regarding this process were once available. Current updates outpace formal documentation frequency, rendering such information elusive.

Ongoing discourse within GitHub addresses the notion of inference site prioritization. Visit various issues like microsoft/TypeScript#14829 concerning request features enabling priority nullification at a site, along with challenges arising from developer expectations misaligning with compiler behavior indicated in issues such as microsoft/TypeScript#39295 and microsoft/TypeScript#32389.

A recurring theme notes intersections like (T & {}) possessing inferior precedence in comparison to non-intersecting types like T. By utilizing T & {} effectively, the precedence of an intervening site can be diminished when interference occurs. Hereby, elucidating why infer R & object fails to secure the role of site selection for P.

Thus, comprehending the exhaustive mechanisms governing these nuances might not always yield enlightening revelations. While scrutinizing the type checker code, assumptions relating to hierarchies like whether return types from construct signatures enjoy higher accord over those from call signatures could be discerned. However, anchoring programs heavily reliant on pronounced intricacies thereof is ill-advised, considering fluctuating inference rules across successive language iterations.


Experience the code in Playground

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

Using interpolation brackets in Angular2 for variables instead of dots

I'm curious if Angular2 has a feature similar to the bracket notation in Javascript that allows you to use variables to select an object property, or if there is another method to achieve the same functionality. Here is the code snippet for reference ...

There seems to be an issue with Firebase authentication on firebase-admin in node.js. Your client is being denied permission to access the URL "system.gserviceaccount.com" from the server

Issue I've been utilizing Firebase auth on my client and using firebase-admin to verify on the server. It was functioning well until I decided to migrate to a different server, which caused it to stop working. The crucial part of the error message i ...

Determine data type based on key of object within a Zod array

Trying to extract the type from a key within an array of objects using Zod presents some challenges, especially when the array is nested within another object. To illustrate the issue: const obj = z.object({ nestedArray: z.array(z.object({ valueIWant: z ...

After each save, gulp-typescript is emitting errors, however, it works without any issues upon subsequent saves

I'm facing some uncertainty regarding whether the issue I'm encountering is related to gulp, typescript, or Angular 2. Currently, I am using Angular 2 Beta 6. Here is an example of my typescript gulp task: var tsProject = p.typescript.createPr ...

Is it possible to utilize the inline/hardcoded type declared in the component.d.ts file for reuse

Is there a way to pass props selectively to the library component? The library has hardcoded multiple values in an inline type. If my code needs to automatically update with any new additions to the library-defined type, can I reuse those inline values r ...

How to assign attributes to all child elements in Angular?

I have a unique component in Angular that I utilize throughout my app. It's a button component which I use by calling <app-delete-btn></app-delete-btn> wherever needed. I tried to set the tabindex="1" attribute for my component ...

Setting an initial value for an @Output EventEmitter in Angular 6 is a breeze

I'm working on a component that includes a dropdown selection menu. <p style="padding: 5px"> <select [(ngModel)]='thisDD' name="nameDD" id="idDD" (ngModelChange)="updateDD(thisDD)" class="form-control"> <o ...

What is the proper way to access the global `angular` variable in Angular and TypeScript when using AngularJS?

I'm currently integrating an Angular 4 component into a large monolithic AngularJS application. The challenge I face lies in the restrictions of the AngularJS project's build system, which limits me to only modifying the project's index.html ...

Eliminating an element from an object containing nested arrays

Greetings, I am currently working with an object structured like the following: var obj= { _id: string; name: string; loc: [{ locname: string; locId: string; locadd: [{ st: string; zip: str ...

Is it possible to use TypeScript or Angular to disable or remove arrow key navigation from a PrimeNG Table programmatically?

Is there a way to programmatically prevent left and right arrow key navigation in a PrimeNG Table with cell editing, without the need to modify the Table component source code? You can check out an example here: Angular Primeng Tableedit Demo code. I mana ...

Creating TypeScript types using GraphQL code generation: Generating types without any other code

Utilizing the typescript plugin for graphql code generator As documented This TypeScript plugin is fundamental and capable of creating typings from GraphQLSchema, which can be leveraged by other typescript plugins. It creates types for all aspects of you ...

Is there a way to automatically extend my content to fill the space on the page below the Material UI AppBar?

I am currently using React and Material UI React to develop my application. My goal is to implement the AppBar component with content underneath, without causing the entire page to scroll. I want the content to occupy the maximum remaining height and the f ...

Have there been any instances of combining AngularJS, ASP.NET-WebApi, OData, Breeze.js, and Typescript?

I am attempting to combine different technologies, but I am facing challenges as the entity framework meta-datas are not being consumed properly by breeze.js. Despite setting up all configurations, it's proving to be a bit tricky since there are no ex ...

Is there a mistake in how I am creating this TypeScript object?

After selecting an item from a dropdown menu, I want to remove the select element and display the selected item in an ag-grid. Although the row is added to the grid successfully, the name/id properties do not appear correctly and the select element remains ...

Dot notation for Typescript aliases

Here are the imports I have in my TypeScript source file: import {Vector as sourceVector} from "ol/source"; import {Vector} from "ol/layer"; This is how Vector is exported in ol/source: export { default as Vector } from './source/ ...

Having trouble with SVG Circles - Attempting to create a Speedometer design

Trying to implement a speedometer in one of the components of my Vue project, but encountering an issue. When I input 0 into my progress calculation for determining the stroke fill, it overlaps with the background circle instead of staying within its bound ...

The technique for accessing nested key-value pairs in a JSON object within an Angular application

After receiving the response from the backend, I have retrieved a nested hash map structure where one hash map is nested within another: hmap.put(l,hmaps); //hmap within hmap When returning the response to the frontend, I am using the ResponseEntity meth ...

Using TypeScript to define task invocation parameters with AWS CDK's CfnMaintenanceWindowTask

Currently, I am utilizing AWS CDK along with the library @aws-cdk/aws-ssm and TypeScript to construct CfnMaintenanceWindowTask. The code example I am working on is derived from AWS CloudFormation documentation, specifically for "Create a Run Command t ...

Ways to efficiently update the API_BASE_URL in a TypeScript Angular client generated by NSwag

Is it possible to dynamically change the API_BASE_URL set in my TypeScript client generated by NSWAG? I want to be able to utilize the same client with different API_BASE_URLs in separate Angular modules. Is this achievable? Thank you for your assistance. ...

Trouble encountered with Axios post request in TypeScript

Currently, I am in the process of integrating TypeScript into my project and while working with Axios for a post request, I encountered an issue. The scenario is that I need to send email, first name, last name, and password in my post request, and expect ...