What is the reason for the lack of propagation of TypeScript extended generics?

I encountered a perplexing situation recently. Let me share a snippet of the code that has been bothering me:

type TInputs<A> = A[] | Iterable<A> | Record<string, A>

type TTest = <A, Src extends TInputs<A>>(src: Src) => (x: A) => A

declare const arr: number[]
declare const t: TTest

t (arr) // inferred as const t: <{}, number[]>(src: number[]) => (x: {}) => {}
// expected const t: <number, number[]>(src: number[]) => (x: number) => number

I can't figure out why A is not being saved here. Any insights into why it's instead inferred as {}? How can I resolve this issue?

Thank you in advance for any assistance.
Seb

Answer №1

The TypeScript compiler doesn't make use of a generic constraint for inference positioning. Check out this query on Github to delve into this matter further. Essentially, when you invoke t(arr), the compiler deduces Src as number[], but it can't leverage the resulting constraint number[] extends A[] to infer A. As there are no other cues to infer A, the process fails and yields an empty type {}.

To address this issue... it might not be necessary to have dual type parameters. If your goal is for A to always represent the element type of array Src, you can simply extract that element type using a lookup of the number-indexed property type of

Src</code, like this: <code>Src[number]
instead of A:

type TTest = <Src extends any[]>(src: Src) => (x: Src[number]) => Src[number]

declare const arr: number[]
declare const t: TTest
t(arr)  // const t: <number[]>(src: number[]) => (x: number) => number

This method seems to work effectively. I trust this explanation will be helpful to you. Best wishes!


UPDATE

In light of your updated types, one potential solution involves utilizing conditional types and infer to derive A from

Src</code, like so:</p>

<pre><code>type AFromSrc<Src extends TInputs<any>> = Src extends TInputs<infer A> ? A : never;
type TTest = <Src extends TInputs<any>>(src: Src) => 
  (x: AFromSrc<Src>) => AFromSrc<Src>

If the compiler can accurately determine A from TInputs<A>, this approach should function smoothly. It's crucial to ensure that TInputs<> provides enough transparency for the inference algorithm. Experiment with this solution to see if it fits your requirements. Otherwise, consider alternative methods.

If the above method proves successful, you may opt for a simpler signature such as:

type TTest = <A>(src: TInputs<A>) => (x: A) => A

This alternative retains A while deriving

Src</code from it. This strategy eliminates the need for conditional types but necessitates the compiler to infer <code>A
from a value of type
TInputs<A>. Determine whether this method works effectively for your situation or if adjustments are needed.</p>

<p>Wishing you all the best once again.</p>
    </div></answer1>
<exanswer1><div class="answer accepted" i="53861092" l="4.0" c="1545256077" m="1545268546" v="2" a="amNhbHo=" ai="2887218">
<p>The TypeScript compiler will not use a generic constraint as an inference position.  See <a href="https://github.com/Microsoft/TypeScript/issues/20963" rel="nofollow noreferrer">this similar GitHub question</a> for discussion about it.  That means that while <code>t(arr)
causes
Src</code to be inferred as <code>number[]
, the resulting constraint number[] extends A[] cannot be used to infer A. And since nothing else can be used to infer A, the inference fails with the "I give up" empty type {}.

To fix it... you probably don't actually need two type parameters. If you want A to always be the element type of the Src array, then you can just get that element type by doing a lookup of Src's number-index property type, that is: Src[number] in place of A:

type TTest = <Src extends any[]>(src: Src) => (x: Src[number]) => Src[number]

declare const arr: number[]
declare const t: TTest
t(arr)  // const t: <number[]>(src: number[]) => (x: number) => number

Looks like it works to me. Hope that helps. Good luck!


UPDATE

Given your new types, one might use conditional type and infer to get A from

Src</code, like this:</p>

<pre><code>type AFromSrc<Src extends TInputs<any>> = Src extends TInputs<infer A> ? A : never;
type TTest = <Src extends TInputs<any>>(src: Src) => 
  (x: AFromSrc<Src>) => AFromSrc<Src>

This should also work as long as the compiler can infer A from TInputs<A>. That depends on TInputs<> being transparent enough to the inference algorithm. You should check to see if it works for you. If not you might have to be more clever.

But if it does work then you might want to use the following, simpler signature:

type TTest = <A>(src: TInputs<A>) => (x: A) => A

That is, keep A and compute

Src</code from it.  This involves no conditional types but does expect the compiler to be able to infer <code>A
from a value of type TInputs<A>. If it can, great. If not, you will need to use the first signature and a custom AFromSrc that helps the compiler determine A.

Okay, good luck again.

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

Prisma and Next.js: Changes to content require re-deployment for updates to take effect

Just recently, I launched a new website on Vercel. My web application is being built with Prisma and Next.js. However, I'm currently facing an issue where the content doesn't update in real-time unless I manually re-deploy the application. Here&a ...

Typescript polymorphism allows for the ability to create various

Take a look at the following code snippet: class Salutation { message: string; constructor(text: string) { this.message = text; } greet() { return "Bonjour, " + this.message; } } class Greetings extends Salutation { ...

[deactivated]: Modify a property's value using a different component

One of the requirements for my button is that it should be disabled whenever the callToActionBtn property is true. match-component.html <button [disabled]="callToActionBtn" (click)="sendTask()>Send</button> match-component.ts public callToA ...

You cannot use objects as valid children in React layout components

I encountered an issue with my layout: Unhandled Runtime Error Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead. The code in my DashboardLayout file (dashboardLa ...

The property 'owlDateTimeTrigger' cannot be bound to 'span' as it is not recognized

I have integrated the OwlDateTimeModule into my smart-table-datepicker component. Although I imported it in my smart-table-datepicker.module file, I am still encountering errors. What could be causing this issue? smart-table-datepicker.module.ts import { ...

Can anyone explain the reason behind deserializeUser not being triggered in Angular and Express?

I have exhaustively researched all the questions that are relevant to mine on stack overflow and other platforms... Despite that, I am unable to resolve my issue... My tech stack includes Angular and Express... I am utilizing withCredentials Here is my ...

Exploring the File Selection Dialog in Node.js with TypeScript

Is it possible to display a file dialog in a Node.js TypeScript project without involving a browser or HTML? In my setup, I run the project through CMD and would like to show a box similar to this image: https://i.stack.imgur.com/nJt3h.png Any suggestio ...

What is the best way to divide data prior to uploading it?

I am currently working on updating a function that sends data to a server, and I need to modify it so that it can upload the data in chunks. The original implementation of the function is as follows: private async updateDatasource(variableName: strin ...

Why does the type checking for props in vue.js keep failing despite my use of "Object as PropType<GeographicCoordinate | null>"?

Scenario: Utilizing vue.js (^3.2.13) with Typescript and Composition API in Visual Studio Code File type.ts: export class GeographicCoordinate { latitude: number; longitude: number; altitude?: number; constructor(latitude: number, longitude: numb ...

Dividing points in half at the top and bottom edges in a chart using chartjs

https://i.sstatic.net/AfosF.png Upon observation of the provided image, it can be seen that the points are halved when they reach the top or bottom edges, specifically when the data points are 1 or 5 in this context. Various attempts were made to address ...

typescript set parameter conditionally within a function

For my upcoming app, I am working on an API that will utilize Firebase FCM Admin to send messages. Below is the code snippet: import type { NextApiRequest, NextApiResponse } from "next"; import { getMessaging } from "firebase-admin/messaging ...

Exploring the usage of arrays within Angular 4 components. Identifying and addressing overlooked input

I'm struggling with array declaration and string interpolation in Angular 4 using TypeScript. When I define the following classes: export class MyArrayProperty { property1: string; property2: string; } export class MyComponent { @Input() object: ...

Adding a class to a child component layout from a parent component in Angular 12 and Typescript can be achieved by using the ViewChild decorator

Incorporating the child component into the parent component is an important step in the structure of my project. The dashboard component serves as the child element, while the preview component acts as the parent. Within the parent (preview) component.htm ...

The data type 'string' cannot be assigned to the data type 'Position'

Currently, I am in the process of converting React js to typescript. The component being used is a Class Component. I would like to obtain CSS settings through props and apply them to an element. How can I resolve this issue? render(){return( <span st ...

The real file that was brought in after indicating a module in the node_modules directory that was coded in Typescript

After creating a typescript module named moduleA, I am ready to publish this package and make it accessible from another typescript project. Currently, for testing purposes, I have installed 'moduleA' using 'npm install ../moduleA'. Th ...

Can you provide a guide on setting up and utilizing mathlive within NuxtJS?

Can anyone assist me? I am trying to figure out why my code is not working or if I have implemented it incorrectly. I used npm i mathlive to obtain input. However, following the instructions for implementing nuxt plugins in the documentation has not yield ...

Implement dynamic changes to CSS variable values using Typescript during runtime

My angular 4 web application requires dynamic styling based on color values and image URLs received from the backend. While I have successfully set up the app to load images from the server, handling color values poses a greater challenge. The primary colo ...

Guide to incorporating external code in InversifyJS without direct control

I'm wondering if it's feasible to add classes that are not editable. Inversify seems to rely heavily on annotations and decorators, but I'm curious if there is an alternative method. ...

When using TypeScript with custom components as children in React, the `type` returned by React.Children is a string representing the function

It might sound a bit odd, or maybe I'm completely off track here. While going through some articles and React documentation on getting children and identifying specific child components using React.Component.map(), I ran into an issue with my custom c ...

Setting up a global CSS and SASS stylesheet for webpack, TypeScript, Phaser, and Angular: A step-by-step guide

A manual configuration has been set up to accommodate all the technologies mentioned in the title (webpack, typescript, phaser, and angular). While it works perfectly for angular component stylesheets, there seems to be an issue with including a global st ...