How come TypeScript deduces a generic type in a differing manner when a property involving it is present?

I am working on creating a function that takes a props object as input, where the return type of one property serves as a constraint for the arguments of another property.

Take a look at the following code snippet:


// Defining a base type and another type that extends it

interface Base { x: string; }
interface Foo extends Base { y: number; }

// Creating a function type that accepts Foos as argument

type Fn<T> = (t: T) => void
declare const FooFn: Fn<Foo>

// Now, I want to use the return type of 'x' to restrict the type of function assigned to 'y' contravariantly

type Baz = <R extends Base, F extends Fn<R>>(arg: {
  x: (bar: Foo) => R
  y?: F[]
}) => R;

// Notice that when trying to set 'y', FooFn generates an error
// Even though R is correctly inferred from 'x' (see R1)

declare const baz: Baz;
const R1 = baz({ x: (ctx) => ctx })
const R2 = baz({ x: (ctx) => ctx, y: [FooFn] }) 

The change in return type inference based on whether 'y' is declared is intriguing.

Using NoInfer in F has not resolved this issue.

Why does the return type become Context when 'y' is specified, but MyContext when 'y' is not included?

To clarify, my objective is to allow 'y' to be set as an array of functions that can accept the return type of 'x'.

I have also published this on a TS playground: https://tsplay.dev/mxpM7m

Answer №1

The TypeScript inference algorithm encounters a limitation when handling generic functions and unannotated callback parameters. This issue is extensively discussed in microsoft/TypeScript#47599. In situations where type arguments are not explicitly provided for a call to a generic function, TypeScript tries to infer them on its own. Similarly, when callback function parameters lack annotations, TypeScript attempts to deduce them based on the context. However, problems arise when both type arguments and callback parameters need simultaneous inference, particularly if they are interdependent, leading to potential inference failures due to the internal workings of TypeScript's heuristic algorithm.

Examining microsoft/TypeScript#47599 sheds light on the fact that successful inference relies on various factors that may not initially appear relevant. The concurrent inference of type arguments and callback parameters in TypeScript proves to be delicate, sometimes yielding unexpected outcomes owing to their interconnected nature.


For instance:

const R1 = baz({ x: (ctx) => ctx })

In scenarios like this, TypeScript may struggle to infer F accurately since it prioritizes inferring

R</code over everything else. Employing contextual typing aids in determining the type of <code>ctx
, allowing for the desired inference of
R</code. Alternatively, dealing with:</p>
<pre><code>const R2 = baz({ x: (ctx) => ctx, y: [FooFn] }) 

Presents a challenge for TypeScript to infer F</code from <code>FooFn without prior knowledge of

R</code,</p>
<p>While improvements have been made in TypeScript, such as those introduced in TypeScript 4.7 highlighted in <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#improved-function-inference-in-objects-and-methods" rel="nofollow noreferrer">this update</a> implemented in <a href="https://github.com/microsoft/TypeScript/pull/48538" rel="nofollow noreferrer">microsoft/TypeScript#48538</a>, the underlying issue persists. Embracing suggestions like utilizing intersections instead of constraints might help evade inference setbacks.</p>
<p>If encountering challenges with inference, resorting to manual parameter annotations or exploring alternative approaches like intersections could mitigate potential issues. Remember, facilitating TypeScript in accurately identifying types can prevent unforeseen errors.</p>
<p><a href="https://www.typescriptlang.org/play/?ts=5.4.5#code/JYOwLgpgTgZghgYwgAgEJwM4oN7IB4BcyGYUoA5gNzIC+AUKJLIigGID27yEekIAJhjSYcyAJ5EQAVwC2AI2jV6dMGIAObEAB4AKgD5kAXmQAKMER0BKIwYBu7YPzr8ICADZwoKBOxAlkHOysIETBWoF6dCrqKOgAXkbIWgBK3LwQAkLoWAA0AWl8ggHayXp6Jp7kRNh0yPhEJnKeoZzWhgbJteIA-A2syABkxSl6lgDaALp0NG0dlFEu7p7evv5NcUTx8z5+YMjJAIyJ6ya4hKYIYHizyJd4tJZ0O-7JAEzHcHGn9RdXN3d5CTIMaBYITB5RZ57ZIAZg+XzODTuLXY-yugKIIM4YIelCAA" rel="nofollow noreferrer">Access Playground link here</a></p>
</div></answer1>
<exanswer1><div class="answer" i="78539540" l="4.0" c="1716807773" v="2" a="amNhbHo=" ai="2887218">
<p>The functionality of TypeScript's inference algorithm encounters a known restriction, as specified in <a href="https://github.com/microsoft/TypeScript/issues/47599" rel="nofollow noreferrer">microsoft/TypeScript#47599</a>. When invoking a <a href="https://www.typescriptlang.org/docs/handbook/2/generics.html" rel="nofollow noreferrer">generic</a> function without explicitly defining the type arguments, TypeScript endeavors to deduce them. Similarly, when crafting a callback function without providing <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#parameter-type-annotations" rel="nofollow noreferrer">parameter annotations</a>, TypeScript seeks to infer them <a href="https://www.typescriptlang.org/docs/handbook/type-inference.html#contextual-typing" rel="nofollow noreferrer">from context</a>. In cases where your generic function call involves unannotated callbacks, necessitating the need for dual inference, and if these types depend on each other, there arises a possibility of inference failure due to the sequencing of TypeScript's heuristic algorithm execution.</p>
<p>Delving into microsoft/TypeScript#47599 elucidates that the success of inference hinges on variables that don't seem consequential at first glance. Concurrently inferring type arguments and callback parameters in TypeScript emerges as intricate, susceptible to unpredictable outcomes due to their intertwined dependence.</p>
<hr />
<p>In a specific scenario like:</p>
<pre><code>const R1 = baz({ x: (ctx) => ctx })

This situation showcases TypeScript struggling to infer F accurately, focusing primarily on inferring

R</code. Contextual typing becomes pivotal in establishing <code>ctx
's type, thus enabling the intended inference of
R</code. Conversely, when confronted with:</p>
<pre><code>const R2 = baz({ x: (ctx) => ctx, y: [FooFn] }) 

It becomes challenging for TypeScript to deduce F from FooFn devoid of initial inference for R,

While TypeScript has undergone enhancements, such as those featured in TypeScript 4.7 expounded upon in this update enacted via microsoft/TypeScript#48538, the fundamental issue persists. Implementing recommendations like employing intersections rather than constraints could aid in evading inference setbacks.

If faced with inference difficulties, opting for manual parameter annotations or exploring alternative methodologies like intersections could alleviate possible impediments. Remember, aiding TypeScript in precisely recognizing types can preempt unwarranted errors.

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

Issue with Angular/Chrome: The filter pipe is not able to be located

Even though this topic has been covered before, I have not found any satisfactory solutions. Here is my approach: play.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; impor ...

how to make a "select all" checkbox with Angular 2

`I'm currently working on a feature that allows a checkbox to select all checkboxes within a specific div when checked. The div exclusively contains checkboxes. //TS FILE constructor() { } checkAll: ElementRef | undefined; selectAll(isChecked: ...

PrimeNG Template not showing the form

I have integrated a form into PrimeNG's turbotable to allow users to create a new entry (group) in the table located within the footer. However, the form is not being displayed as expected. Can you help me troubleshoot this issue? <ng-template pTe ...

Need for utilizing a decorator when implementing an interface

I am interested in implementing a rule that mandates certain members of a typescript interface to have decorators in their implementation. Below is an example of the interface I have: export interface InjectComponentDef<TComponent> { // TODO: How ...

Make sure to pause and wait for a click before diverting your

Having an issue with a search dropdown that displays suggestions when the search input is focused. The problem arises when the dropdown closes as soon as the input loses focus, which is the desired functionality. However, clicking on any suggestion causes ...

Creating an object with mapped properties from enumeration values using Typescript

I am trying to find a way to automatically create an object with values computed during compilation using enumeration values and pre-defined function calls. The basic concept is to associate certain arguments of a function with keys. For example, consider ...

Troubleshooting Async Function compatibility between Express and NestJs

Initially, I set up a small express server to handle report generation and file writing tasks. var ssrs = require('mssql-ssrs'); var fs = require('fs'); const express = require('express') const app = express() const port = 30 ...

Adding a component to a page in Angular 4: A step-by-step guide

Currently engaged in a project that involves creating a mobile application design within a web application. I'm wondering how to display my Component object on a page using ViewChild? I have a list of PageComponents, below is the PageComponent class ...

Taking in user inputs using Angular 8 reactive forms

My goal is to make my forms function as intended, with multiple forms on each product item having an update and delete option. However, I encountered an issue when adding the [formGroup]="profileForm" directive - the form controls stopped working. This was ...

What is the best way to pass a form result from a parent component to a child component

In the setup, there is a child component containing a form with various options for selection. The goal is to capture the user's selection and process it within this child component. Despite attempting to utilize an event in the parent component, the ...

Pixijs is unable to load spritesheets correctly

I am currently facing an issue while trying to load a spritesheet in PixiJS following the instructions provided on Below is the code snippet I am using: PIXI.Loader.shared.add('sheet', require('../assets/spritesheet.json')).load(sprite ...

Switch up the data format of a JSON file that is brought into TypeScript

When bringing in a JSON file into a TypeScript project with the resolveJsonModule option activated, the TypeScript compiler can automatically determine the type of the imported JSON file. However, I find this type to be too specific and I would like to rep ...

Issue with React.js code not being detected in TSX file (Visual Studio 2015 Update 1 RC)

Currently, I am utilizing Visual Studio 2015 with update 1 release candidate. Interestingly, I have managed to successfully incorporate React.js code and syntax highlighting within a .JSX file. However, when it comes to a .TSX file, nothing seems to be wor ...

What is the best way to incorporate Typescript React Components across various projects?

I'm venturing into developing an npm package that involves several react components implemented with typescript. As a newcomer to react and npm, I apologize if my query seems basic. Despite researching online, there isn't much information on this ...

To access the value of a DOM input in an Angular component, utilize the "renderer/renderer2" method

Recently, I embarked on my journey to learn Angular. One of my goals is to retrieve data from a form and store it in a database (Firebase) using angularfire2. While going through the documentation, I noticed that there is a "setValue()" method available b ...

Creating a dynamic link for a button based on the selected value from a dropdown menu

Here is an example related to my inquiry example.component.html <div class="center"> <div class="form-group" > <label>Choose a Country</label> <select class="form-control"> <option *ngFor="let option of options">{{op ...

Inheritance of Generic Types in TypeScript

Could someone assist me in understanding what is incorrect with the code snippet provided here? I am still learning Typescript. interface ICalcValue { readonly IsNumber: boolean; readonly IsString: boolean; } interface ICalcValue<T> ex ...

Configuring ordered imports in TSLint

Need help with configuring my TSLint rule ordered-imports. I want the import order to be like this: // React import React from 'react'; import { View } from 'react-native'; // Libs import * as _ from 'lodash'; import * as mo ...

Angular 2 - component for organizing page elements

In the midst of a major project, I encountered an issue involving pages that lacked templates - meaning they did not have the necessary code to wrap around the main content such as a navigation bar and footer. These template-less pages were inaccessible to ...

Determine whether there is only one array in the object that contains values

At the moment, I am attempting to examine an array in order to determine if only one of its elements contains data. Consider this sample array: playersByGender = { mens: [], womens: [], other: [] }; Any combination of these elements may contain dat ...