When the first argument is missing, using a recursive constraint default can result in the incorrect inference of the second argument, especially when both arguments share the same generic

Currently, I am developing a TypeScript implementation of a recursive binary search tree (BST) data structure using generic constraints. In order to establish a default for the recursive generic variable T without directly using it in the default declaration (as that is not allowed), I have employed co-recursive logic as a 'base case' for the constraint. The code snippet below illustrates this approach:

// Utilizing bounded polymorphism with recursive generic constraints.
type IBSTBoRecDflt<K, V> = IBSTBoRec<K, V, IBSTBoRecDflt<K, V>>;

interface IBSTBoRec<K, V, T extends IBSTBoRec<K, V, T> = IBSTBoRecDflt<K, V>> {
    key: K;
    value: V;
    left?: T;
    right?: T;
}

type BSTBoRecDeflt<K, V> = BSTBoRec<K, V, BSTBoRecDeflt<K, V>>;

class BSTBoRec<K, V, T extends BSTBoRec<K, V, T> = BSTBoRecDeflt<K, V>> implements IBSTBoRec<K, V, T> {
    key: K;
    value: V;
    left?: T;
    right?: T;

    constructor(key: K, value: V, left?: T, right?: T) { 
        this.key = key;
        this.value = value;
        this.left = left;
        this.right = right;    
    }
}

const t = new BSTBoRec(5, 'e', undefined, new BSTBoRec(8, 'h'));

An issue arises where the type inference seems to be incorrect for the right branch (8, h) when a left branch is missing. Upon executing new BSTBoRec(8, 'h'), we encounter the following error on the last line:

Argument of type 'BSTBoRec<number, string, BSTBoRec<number, string, undefined>>' is not assignable to parameter of type 
                 'BSTBoRec<number, string, BSTBoRec<number, string, BSTBoRec<number, string, undefined>>>'.
  Type 'BSTBoRec<number, string, undefined>' is not assignable to type 
       'BSTBoRec<number, string, BSTBoRec<number, string, undefined>>'.
    Type 'undefined' is not assignable to type 'BSTBoRec<number, string, undefined>'.

The error can be resolved by explicitly specifying the types K and V for the (8, h) branch:

const t = new BSTBoRec(5, 'e', undefined, new BSTBoRec<number, string>(8, 'h'));

In conclusion, there seems to be an issue with the type inference specifically when the left branch is omitted. Any insights into this behavior would be greatly appreciated.

For reference, you can view a live example of the code and error on this playground link.

Answer №1

Without delving into the compiler code, it's difficult to pinpoint the exact reason why the inference is failing in this case. A similar issue has been raised on GitHub regarding invalid generic type arguments not meeting the constraints as outlined here: microsoft/TypeScript#45286. In my scenario, it seems like the inference for 'T' defaults to undefined (the type of 'left'), leading to unexpected types being inferred within the contextual typing of 'new BSTBoRec(8, 'h')'. This behavior contradicts the expected constraint on the type parameter 'T'. It appears that TypeScript does not handle this situation appropriately, potentially due to a bug or design limitation that may never be resolved.


If I were to address this problem, one approach could involve adjusting the placement of 'T' within the constructor parameters to provide better guidance for inference. A minimal change to achieve this would be:

constructor(key: K, value: V, left?: BSTBoRec<K, V, T>, right?: BSTBoRec<K, V, T>) {
    this.key = key;
    this.value = value;
    this.left = left as T;
    this.right = right as T;
}]

This alteration reduces the precedence of inferring 'T', resulting in successful compilation (though explicit type assertions are needed during assignment since there is potential for failure).


An alternative fix could involve overloading the constructor function or employing a union of tuple types as a rest parameter:

constructor(key: K, value: V, ...[left, right]: [undefined, T] | [T?, T?]) {
        this.key = key;
        this.value = value;
        this.left = left as T;
        this.right = right as T;
    }

In this setup, 'undefined' assigned to 'left' influences the inference of 'T' towards 'right,' offering a workaround to mitigate issues. However, some undesired type artifacts may persist, making the earlier suggestion preferable.


Overall, dealing with complicated recursive generic type defaults can lead to peculiar inference behaviors, necessitating caution while working through such scenarios.

Check out the playground link for the code snippet

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

How to trigger a function in a separate component (Comp2) from the HTML of Comp1 using Angular 2

--- Component 1--------------- <div> <li><a href="#" (click)="getFactsCount()"> Instance 2 </a></li> However, the getFactsCount() function is located in another component. I am considering utilizing @output/emitter or some o ...

Personalized angular subscription malfunction

Recently, as I dive into learning Angular 6, a question has arisen regarding the subscribe method and error handling. A typical use of the subscribe function on an observable typically appears like this: this.myService.myFunction(this.myArg).subscribe( ...

Updating the state of a nested array using React Hooks

After spending some time working with React Hooks, my main struggle has been dealing with arrays. Currently, I am developing a registration form for teams. Each team consists of a list of players (an array of strings). The goal is to allow users to add t ...

Having trouble locating the export in the TypeScript module

Having a situation where there is a file with an exported object: let btypes:{[key:string]:any} = { "key1":val, //... } export {btypes} I even attempted to use export default btypes Upon importing it using: import {btypes} from "../types& ...

Is it possible to effortlessly associate a personalized string with an identifier within an HTML element utilizing Angular2?

Check out this cool plunker import {Component} from 'angular2/core' @Component({ selector: 'my-app', template: ` <div *ngFor="#option of myHashMap"> <input type="radio" name="myRadio" id="{{generateId(option[& ...

Send empty object using FormBuilder

When retrieving an object from a backend API service like this: data: {firstName:'pepe',lastName:'test', address = {street: 'Cervantes', city:'Villajoyosa'} } or data: {firstName:'pepe',lastName:'test ...

Constructing objects in TypeScript is a breeze with its C#-ins

It seems like finding a simple solution for my task is proving to be quite challenging. I have a class that accepts 10 parameters, most of which are optional. To simplify things, I will illustrate my dilemma using just 3 parameters. I wish to be able to i ...

Upon clicking a button, I aim to retrieve the data from the rows that the user has checked. I am currently unsure of how to iterate through the rows in my mat-table

My goal is to iterate through the column of my mat-table to identify the rows that have been checked, and then store the data of those rows in an array. <td mat-cell *matCellDef="let row"> <mat-checkbox (click)="$event.stopPropagation()" (c ...

Angular is not able to access the value of a promise in a different function after it has been retrieved

I have a form with default values that should be added to the form fields when it appears. There are two functions: #1 for fetching data from the backend and #2 for displaying the form: person$: any; ngOnInit() { this.getPersonData(123) this.buildPer ...

Employ an asynchronous immediately-invoked function expression within the callback

Can an asynchronous IIFE be used inside the callback function to avoid the error message "Promise returned in function argument where a void return was expected"? You can find an example here. signIn(email: string, password: string, course?: ICourse): ...

Error: Code layer not located while utilizing "sam invoke local" in AWS

Currently, I am engaged in an AWS project where I am developing two lambda functions. Both of these functions rely on a common codebase stored in the node_modules directory, which is placed in a separate layer named AWS::Lambda::LayerVersion, not to be con ...

Incorporating Typescript with Chart.js: The 'interaction.mode' types do not match between these two entities

I am working on developing a React Functional Component using Typescript that showcases a chart created with chartjs. The data and options are passed from the parent component to the child component responsible for rendering the line chart. During the pr ...

Tips for enabling users to import from subdirectories within my NPM package

Is there a way to allow users to import from subfolders of my TypeScript NPM package? For instance, if the TypeScript code is structured like this: - lib - src - server - react Users should be able to import from the subfolders as package-name/react, ...

No declaration file was located for the module '@handsontable/react' despite the presence of a 'd.ts' file

Embarking on a fresh project using vite+react+ts+swc by executing the command below as per the vite documentation. npm create vite@latest -- --template react-swc-ts Additionally, I integrated the handsontable library into my setup with the following comm ...

Implementing Angular 4 to fetch information from a JSON file

As a beginner in Angular, my current task involves loading data from a JSON file upon click, which I have successfully achieved so far. However, I am facing an issue where I'm unable to load the first JSON object before clicking, meaning that I want ...

Guide to setting up .env Variables on a DigitalOcean Ubuntu droplet

After deploying my Node.js app on a DigitalOcean Ubuntu droplet, I encountered the need for variables from my .env file. How can I go about creating these variables within the DigitalOcean droplet? ...

I'm receiving an error message stating "mongoose.connect is not a function" while attempting to establish a connection with mongoose. Can you help me troub

I'm a beginner in Node.js and I'm currently working on creating a node/express/mongoose server app using TypeScript. Below is my app.ts file: // lib/app.ts import express from 'express'; import * as bodyParser from 'body-parser&a ...

What causes the constant reappearance of props as a parameter in my Home function?

I'm currently in the process of developing an app, and I keep encountering an error related to "props" in my index.tsx file 'props' is declared but its value is never read.ts(6133) Parameter 'props' implicitly has an 'any&apos ...

Developing mongoose models using TypeScript for subdocuments

Exploring the integration of mongoose models with typescript, following a guide available at: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models. Unsure how arrays of subdocuments align with this setup. For instance, consider the model ...

TSX: Interface Definition for Nested Recursive Array of Objects

I'm having trouble making my typescript interface compatible with a react tsx component. I have an array of objects with possible sub items that I need to work with. Despite trying various interfaces, I always run into some kind of error. At the mome ...