In Typescript, issues arise when trying to assign types with inheritance in Generics - Type mismatch detected

As I work on creating a generic parent class to handle multiple children, I have encountered a challenge. My goal is to define an abstract function in the parent class that will take in a child object and return that same class. Here's my initial attempt:

abstract class Parent<T> {
    constructor(public readonly val: T) { }
    public abstract add<C extends Parent<T>>(other: C): C;
}

Next, let's look at how this concept translates into a child class. In this example, the child class extends Parent<string>, working with a Child object input and output.

class Child extends Parent<string> {
    public add(other: Child): Child {
        return new Child(this.val + other.val);
    }
}

However, an error occurs at the add line:

Property 'add' in type 'Child' is not assignable to the same property in base type 'Parent<string>'.
  Type '(other: Child) => Child' is not assignable to type '<C extends Parent<string>>(other: C) => C'.
    Type 'Child' is not assignable to type 'C'.
      'C' could be instantiated with an arbitrary type which could be unrelated to 'Child'.ts(2416)

This error message is perplexing. Despite explicitly extending Parent<string>, the issue persists. Adding a type to the function signature does not resolve it either:

class Child extends Parent<string> {
    public add<Child>(other: Child): Child {
        return new Child(this.val + other.val);
    }
}

The error now appears on the return line:

Type 'Child' is not assignable to type 'Child'. Two different types with this name exist, but they are unrelated.
  'Child' could be instantiated with an arbitrary type which could be unrelated to 'Child'.ts(2719)

If anyone can provide insight into why these errors are occurring and their implications, I would greatly appreciate it.

A temporary solution is to remove the abstract function from Parent. However, I am seeking a more permanent resolution. Are there alternative strategies I should consider?

abstract class Parent<T> {
    constructor(public readonly val: T) { }
}

Answer №1

There is an issue with the following code snippet:

abstract class Parent<T> {
  constructor(public readonly val: T) { }
  public abstract add<C extends Parent<T>>(other: C): C;
}

class Child extends Parent<string> {
  public add(other: Child): Child { // error for legitimate reason
    return new Child(this.val + other.val);
  }
}

The problem lies in using generics with functions and methods, such as add(), where the generic type parameters are scoped to the call signature rather than the implementation itself. This means that the generic type argument is determined by calling the method, not implementing it. The call signature

<C extends Parent<T>>(other: C) => C
implies that the caller decides what C will be when invoking the method. As a result, allowing any subclass to pass any subtype of Parent<T> into add() effectively turns it into the identity function.

This approach could lead to runtime errors. For instance:

class ProblemChild extends Parent<string> {
  problem = "definitely";
  public add(other: ProblemChild): ProblemChild { // error for legitimate reason
    return new ProblemChild(other.problem.toUpperCase());
  }
}

const c: Parent<string> = new ProblemChild("abc"); // error for legitimate reason
try {
  c.add(new Child("z")); // Allowed, but results in ERROR (other.problem is undefined)
} catch (e) {
  console.log(e);
}

To address this unsafe practice, it is recommended to move the scope of the generic from the method to the class itself. By doing so, each subclass can now choose its own generic type parameter. Here's an example:

abstract class Parent<T, C extends Parent<T, C>> {
  constructor(public readonly val: T) { }
  public abstract add(other: C): C;
}

class Child extends Parent<string, Child> {
  public add(other: Child): Child {
    return new Child(this.val + other.val);
  }
}

By adopting this approach, TypeScript enforces type safety by ensuring that instances of subclasses cannot inadvertently pass the wrong types to the add() method.


If convenience is prioritized over strict typing, bivariant checking can be exploited. Although this method is less safe, it allows for more flexibility. Revisiting the earlier code without generics would look like:

abstract class Parent<T> {
  constructor(public readonly val: T) { }
  public abstract add(other: Parent<T>): Parent<T>;
}

class Child extends Parent<string> {
  public add(other: Child): Child {
    return new Child(this.val + other.val);
  }
}

While convenient, this looser approach can still lead to unforeseen runtime errors due to improper type matching.

In conclusion, weighing between type safety and ease of use depends on the likelihood of encountering issues related to narrowing parameters or widening class structures in your codebase.

Playground link to code

Answer №2

It turns out that there is no need for a generic parameter in the add method after all. When the abstract function accepts and returns Parent<T>, it appears that child classes can override it with Child.

abstract class Parent<T> {
    constructor(public readonly val: T) { }
    public abstract add(other: Parent<T>): Parent<T>;;
}

class Child extends Parent<string> {
    public add(other: Child): Child {
        return new Child(this.val + other.val);
    }
}

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

Encountering 'null' error in template with Angular 4.1.0 and strictNullChecks mode

After updating Angular to version 4.1.0 and activating "strictNullChecks" in my project, I am encountering numerous errors in the templates (.html) that look like this: An object may be 'null' All these errors are pointing to .html templat ...

Retrieve type definitions for function parameters from an immutable array containing multiple arrays

My current challenge involves implementing a function similar to Jest's test.each iterator: // with "as const" forEach([ [ 1, 2, 3 ], [ "a", "b", "c" ], ] as const, (first, second, third) => { // ...

What techniques can I use to adjust the size of an image through zooming in and out?

In my custom gallery component, the crucial code section looks like this: <Gallery> <Header> <img src={galleryIcon} alt='Galley icon' /> <h1>My Gallery</h1> </Header> ...

What causes TypeScript to overlook the generic constraint within a function?

Here is a simple illustration of what I am trying to convey: type Shape = 'square' | 'circle'; type Params<S extends Shape> = S extends 'square' ? { side: number } : { radius: number }; function getArea<S ...

What steps can I take to fix the 'node module error' while deploying a project on Vercel?

While working with the world-countries package, I encountered an issue during deployment in Vercel. The error message indicated that a ';' was missing in the index.d.ts file of world-countries located in the node_module directory. Here is the ex ...

What could be causing this function to malfunction?

Apologies for any inaccuracies in technical terms used here. Despite being proficient in English, I learned programming in my native language. I am currently working on a project using the latest version of Angular along with Bootstrap. I'm unsure if ...

Issue with asynchronous function: "Cannot assign type 'Promise<MyType[]>[]' to type 'MyType[]'"

After converting this function to async, I've been encountering issues with type annotations being out of sync: export default async function formatCallRecordsForPGPromise( rawCalldata: CallRecord[], ): Promise<SaveableCallRecord[]> { const ...

Problem with TypeScript involving parameter destructuring and null coalescing

When using parameter destructuring with null coalescing in TypeScript, there seems to be an issue with the optional name attribute. I prefer not to modify the original code like this: const name = data?.resource?.name ?? [] just to appease TypeScript. How ...

Could a tslint rule be implemented in Typescript classes to ensure method return types are enforced?

After diving into the tslint rules here, it seems that although the typedef rule's call-signature option might be close to what I need, it doesn't address the absence of a return type. Is there a specific rule (if one exists) that can enforce re ...

Bring in an Angular Component into a different Component by stating the name of the component as an input parameter

In my project, I am looking to develop an angle component made up of multiple sub-components. The end goal is to construct a versatile tree component with nodes that cater to different data types. Each data type in the tree component will import specific s ...

Using TypeScript generics to add constraints to function parameters within an object

My Goal: Imagine a configuration with types structured like this: type ExmapleConfig = { A: { Component: (props: { type: "a"; a: number; b: number }) => null }; B: { Component: (props: { type: "b"; a: string; c: number }) =& ...

What is the best way to specify a function parameter as a Function type in TypeScript?

I'm currently delving into the world of TypeScript and I am unsure about how to specify a function parameter as a function type. For instance, in this piece of code, I am passing a setState function through props to a child component. const SelectCity ...

Is the Property Decorator failing to substitute the definition?

My code is similar to the following scenario, where I am attempting to replace a property based on a decorator export function DecorateMe() { return function(component: any, propertyKey: string) { Object.defineProperty(component, propertyKey, ...

Retrieving the value of a selected option in Angular

I have the following dropdown select in my HTML and I am currently retrieving the text content of the selected option. How can I access the value attribute instead? Here is the dropdown select: <form [formGroup]="angForm" class="form-inline my-5 my-l ...

Dynamically pass a template to a child component

How can I dynamically load content on my page based on the active navigation point? export class Sub_navigation_item { constructor( public title: string, public templateName: string ) {} } I have a navigation item with an ID from an ...

"Send the selected radio button options chosen by the user, with the values specified in a JSON format

My current task involves inserting radio button values into a MySql database using Angular. The form consists of radio buttons with predefined values stored in a json file. Below is an example of how the json file is structured: //data.json [{ "surve ...

Having trouble with JavaScript's Date.getUTCMilliSeconds() function?

I have a straightforward question for you. Take a look at this Angular App and try to create a new date, then print the number of UTC milliseconds of that date in the console. Can you figure out why it is returning zero? ...

Create the HTTP POST request body using an object in readiness for submission

When sending the body of an http post request in Angular, I typically use the following approach: let requestBody: String = ""; //dataObject is the object containing form values to send for (let key in dataObject) { if (dataObject[key]) { ...

The React-Typescript error message is stating that the module "react-router-dom" does not have the exported member "RouteComponentProps"

I encountered an issue with my project involving a login page and the usage of "RouteComponentProps". Unfortunately, I received the following error: Module '"react-router-dom"' has no exported member 'RouteComponentProps'. Upon attempt ...

Experiencing a Typescript error when trying to access a property within a nested object

My current challenge involves typing an object, which seems to be error-free until I try to access a nested property and encounter the dreaded red squiggle. After some research, I came across suggestions like this: type FlagValue = string | boolean | numb ...