Let TypeScript determine the appropriate generic type automatically

Below is a simplified code snippet that encounters an issue inferring Set<number> for the inner property:

function test<S>(state: S, fn: (value: { outer: Set<S> }) => void) {}
function numberFn(value: { outer: Set<{ inner: Set<number> }> }) {}
test({ inner: new Set() }, numberFn)
//     ^____
// Type 'Set<unknown>' is not assignable to type 'Set<number>'.
//   Type 'unknown' is not assignable to type 'number'.ts(2322)

In my actual code, I have numerous calls like test with various types for the generic parameter of Set, and currently these types are not exported. Explicitly typing them would be troublesome, so I was hoping TypeScript could automatically infer them, but unfortunately, I have not been able to achieve that.

To gain a better understanding and attempt to solve the issue, I conducted some tests:

function test1<T>(input: { arr: T[] }, value: { val: T }) {}
test1({ arr: [] }, { val: 5 })
// function test1<number>(input: {
//   arr: number[];
// }, value: {
//   val: number;
// }): void

The above test worked successfully. However, the following test did not yield the desired result:

function test2<T>(input: { arr: Array<T> }, value: { val: T }) {}
test2({ arr: new Array() }, { val: 5 })
// function test2<any>(input: {
//   arr: any[];
// }, value: {
//   val: any;
// }): void

This discrepancy may be due to special handling of [] in comparison to generic types in general. Interestingly, when attempting the same scenario using Set instead of Array with reversed argument order, the correct type inference was achieved:

function test3<T>(value: { val: T }, input: { arr: Set<T> }) {}
test3({ val: 5 }, { arr: new Set() })
// function test3<number>(value: {
//   val: number;
// }, input: {
//   arr: Set<number>;
// }): void

To work around this issue, explicit type inference can be performed in TypeScript, although it may become convoluted, as shown below:

function test<S>(state: S, fn: (value: { outer: Set<S> }) => void) {}
function numberFn(value: { outer: Set<{ inner: Set<number> }> }) {}
test(
  { inner: new Set() } as Parameters<typeof numberFn>[0] extends { outer: Set<infer S> } ? S : never,
  numberFn,
)

Is there a way to rewrite the code so that TypeScript can correctly infer the type for state, similar to what occurs in test3?

Answer №1

Typically, TypeScript struggles to deduce both generic type arguments and contextual types simultaneously. When you invoke the test() function, the type argument for S should be inferred from numberFn, implying that the argument {inner: new Set()} needs to match against {inner: Set<number>}. Consequently, new Set() should be interpreted within the context of Set<number>, leading the constructor's type argument to be inferred as number. Unfortunately, the inference algorithm doesn't follow this sequence correctly. At some point, it gives up on contextual typing for state, causing the inference of the Set constructor's type argument to fail and default to unknown. Since Set<unknown> is not compatible with Set<number>, errors occur.

function test<S>(state: S, fn: (value: { outer: Set<S> }) => void) { return state; }      

function numberFn(value: { outer: Set<{ inner: Set<number> }> }) { }

test({ inner: new Set() }, numberFn) // error
//     ~~~~~
// 'Set<unknown>' is not assignable to 'Set<number>'

This issue of conflicting generic and contextual inference represents a broader limitation of TypeScript. The main open problem related to this can be found in microsoft/TypeScript#47599. While progress has been made in addressing this, such as seen in microsoft/TypeScript#48538, there will always be scenarios that aren't fully supported.


The simplest solution is to adapt and use Set<any> instead of Set<unknown>, since any is more flexible than unknown. Note that I altered test() to return

state</code, demonstrating that <code>S
is still inferred as {inner: Set<number>}, resulting in the returned value being of type Set<number> (not Set<any>):

const inner = test({ inner: new Set<any>() }, numberFn).inner; // okay
//    ^? const inner: Set<number>;

If precise inference is a top priority, one approach to gain the necessary control is breaking down test() using currying:

function curryTest<S>(fn: (value: { outer: Set<S> }) => void) {
    return (state: S) => {
        return state;
    }
}

In this modified version, curryTest() only takes fn, inferring

S</code. It returns another function that solely accepts <code>state
, firmly establishing S. There are no simultaneous inferences happening here:

const inner2 = curryTest(numberFn)({ inner: new Set() }).inner; // okay
//    ^? const inner2: Set<number>;

A similar outcome might be achievable in an un-curried function by reordering arguments, as certain parts of the inference process operate sequentially through the argument list. If not, this alternative remains viable.

Link to interactive 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

What are the drawbacks of removing comments from polyfills.ts in Angular CLI when using Internet Explorer?

Encountering a similar problem as described in Angular4 Application running issues in IE11. Although the suggested solution resolved the issue, I am left wondering why the lines referring to necessary polyfills for IE9, IE10, and IE11 were initially comm ...

Retrieve information using Angular's EventEmitter

Recently I started learning Angular and encountered a challenging issue that has kept me occupied for the past few hours. I have implemented a parent-child relationship between two components, with a need to share a boolean variable from the parent to the ...

Steps for exporting various elements from a .vue file

In my Vue project, I am incorporating TypeScript along with Vue. There is a specific scenario where I need to export multiple items from my .vue file. Here's an example of what I want to achieve: // FooBar.vue <template> ... </template& ...

Add fresh data key and corresponding value into an array containing multiple objects

Inserting an accountId key and value into the this.membersForm.value object is the goal, while also setting the id to 0 and adding memberId: id to the this.membersForm.value.teamMembersDto object. How can we achieve this task of inserting the accountId ke ...

Proceed to the subsequent 'then' statement promptly

I'm currently delving into Typescript and exploring functional programming. My project involves a simple process where I query MongoDB, then for each document retrieved, I make a call to SQL Server to fetch additional data. Here is the snippet of code ...

What is the best way to pass a URL as a prop in Next.js without encountering the issue of it being undefined

Within my file (handlers.ts), I have a function designed to post data to a dynamic API route while utilizing the app router. This function requires values and a URL for fetching. However, when I pass the URL as a prop like this: http://localhost:3000/unde ...

What can cause a problem with the reduce function that populates an empty object with keys in TypeScript?

I've encountered an issue with a function that is meant to reduce an object. The problem lies in using the reduce method to assign the values of acc[key] as object[key], which is resulting in errors in the code. I am trying to avoid using any specific ...

Struggling to compile TypeScript code

After updating TypeScript and NodeJS to the latest versions, I decided to test a simple TypeScript example by compiling it using both Node terminal and Windows 10 command line. Here is the code snippet I used: let greeting:string = "Hello!"; console.log(g ...

Trouble Loading TypeScript Class in Cast Situation

I've encountered an issue with my TypeScript model while using it in a cast. The model does not load properly when the application is running, preventing me from accessing any functions within it. Model export class DataIDElement extends HTMLElement ...

Issue with comparing strings in Typescript

This particular issue is causing my Angular application to malfunction. To illustrate, the const I've defined serves as a means of testing certain values within a function. Although there are workarounds for this problem, I find it perplexing and woul ...

Tips for linking a composite element while utilizing React's forward ref (attribute is not present on forwardRefExoticComponent)

In my Panel component, I have implemented the option for users to utilize compound components. The default header of the panel includes a close button, but I designed it this way to give users the flexibility to create their own custom headers with differe ...

Guide to leveraging tanstack table v8 for sorting data within a specific date range

Received data from API: const abc = [ { date: '2023-12-8', value: 'mop' },{ date: '2023-10-8', value: 'qrs' } ] How can we create a date range using two input fields when the dates are in string forma ...

Troubleshooting a Pulumi script in Azure using Typescript and encountering difficulties with function writing

My background is in using terraform, but now I am trying out Pulumi/typescript for the first time. In my codebase, I have two files - index.ts and blob.ts. The create function in blob.ts is responsible for creating a storage account, resource group, blob ...

A guide on obtaining the date format according to locale using Intl.DateTimeFormat within JavaScript

Can someone assist me in obtaining the standard date format (such as MM/DD/YYYY) based on a specified local id? The code snippet provided below is not returning the desired format. Any guidance on how to achieve this would be greatly appreciated. var da ...

The concept of an undefined object without a question mark is frowned upon

Consider the following code snippet: app-config.json { "auth": { "clientId": "acb610688b49", }, "cache": { "cacheLocation": "localStorage" }, "scopes": { "loginRequest": ["openid", "profile", "user.read"] }, "resources": { " ...

The JSON property is showing as undefined despite the fact that the variable holding the data is filled with a JSON object

My current challenge involves retrieving a MongoDB document from my database using a get request. I have successfully tested the URL via Postman and it returns the document as expected, indicating that the issue lies within the frontend rather than the ser ...

Transforming a TypeScript enum into an array of objects

My enum is defined in this structure: export enum GoalProgressMeasurements { Percentage = 1, Numeric_Target = 2, Completed_Tasks = 3, Average_Milestone_Progress = 4, Not_Measured = 5 } However, I want to transform it into an object ar ...

The Angular Material DatePicker fails to update in accordance with the Moment locale settings

Issue: Inconsistency with Angular Material 7 DatePicker and moment js integration Challenge: The datepickers fail to update their locale when the language of the web application is changed. Objective: I aim to have my date pickers automatically adjust to ...

What is the best way to display toastr messages in an Angular application?

Can you guide me on how to include toastr in an angular app? Currently, I am enrolled in the Angular Fundamentals course and trying to use toastr.success within my export class: handleThumbnailClick(eventName){ toastr.success(eventName) } But, I kee ...

Utilize the class or interface method's data type

In the context of a child component calling a callback provided by its parent, this situation is commonly seen in AngularJS. As I am utilizing TypeScript, I aim to implement strong typing for the callback in the child component. Here is the initial stat ...