Is subtyping causing issues in TypeScript's inheritance model?

I am currently utilizing TypeScript for my coding projects, and I have observed that it can allow the production of non-type-safe code. Despite implementing all the "strict" options available to me, the behavior I am experiencing goes against the principle of "Inheritance implies subtyping". This issue has been discussed in various platforms, such as:

https://stackoverflow.com/questions/50729485/override-method-with-different-argument-types-in-extended-class-typescript

One specific piece of code that does not raise any type errors is as follows:

abstract class A {
    abstract do(x: number | string): number;
}

class B extends A {
    override do(x: number): number {
        return x;
    }
}

const a: A = new B();

const x: number = a.do("dupa");

console.log(x);

Instead of receiving an expected error message like:

Error:(7, 14) TS2416: Property 'do' in type 'B' is not assignable to the same property in base type 'A'.
  Type '(x: number) => number' is not assignable to type '(x: string | number) => number'.
    Types of parameters 'x' and 'x' are incompatible.
      Type 'string | number' is not assignable to type 'number'.
        Type 'string' is not assignable to type 'number'.

The output actually displays "dupa" in the console.

In attempt to troubleshoot this issue, I tried switching types (X, Y) from (number, string) to different pairs under the assumption that there might be implicit casting involved. However, even with pairs of arbitrary or non-assignable types X and Y, or with some combination where X and Y=null (while working with strictNullChecks), I encountered the same outcome.

Furthermore, I was able to deliberately create a type error

Type 'string | number' is not assignable to type 'number'.   Type 'string' is not assignable to type 'number'.
. Hence, in general, such assignments should not be permitted.

It appears that this behavior could potentially be considered as a "feature rather than a bug", as indicated in: https://github.com/microsoft/TypeScript/issues/22156

Thus, I am now seeking to rephrase the inquiry:

Is there a workaround available that would prompt the TypeScript type checker to detect the absence of contravariance in parameter types within overridden methods?

Answer №1

To achieve true soundness and type-safety in TypeScript, it would be necessary for the language to compare function and method parameters contravariantly. This would ensure that method overrides only widen parameter types without narrowing them. However, TypeScript is not designed to be truly sound, as stated in the TypeScipt design goals. The goal is to balance correctness and productivity rather than aim for a provably correct type system.

In TypeScript, method parameters are compared bivariantly, allowing both widening and narrowing of parameter types when overriding methods. Even though non-method function types became strict with the introduction of the --strictFunctionTypes compiler option, method parameters remain bivariant regardless of this setting.

There are reasons behind this decision, one being that developers often want to treat arrays covariantly, leading to potential unsound behavior. Similar unsoundness exists with properties in TypeScript, where covariance can lead to unexpected runtime errors.

Enforcing soundness for methods while properties remain unsound creates inconsistencies in the language. Therefore, TypeScript continues to allow unsafe method overrides but restricts standalone functions and callback function types.


If you need a workaround, consider using function-valued properties instead of methods in your code. This can help avoid conflicts with method overrides by defining these functions syntactically within your type definitions.

interface A {
    do: (x: number | string) => number;
}

class B implements A {
    do(x: number): number { // error!
        return x;
    }
}

You could also use abstract classes with overridden function properties instead of methods to maintain the structure required by your code:

abstract class A {
    abstract do: (x: number | string) => number;
}

class B extends A {
    override do = function (x: number) { // error            
        return x;
    }
}

For more information on working around TypeScript limitations, check out the provided Playground link which showcases different approaches to handling type-related issues.

Answer №2

There appears to be a common saying in the coding world, that it's more of a feature than a bug, as detailed here:

https://github.com/microsoft/TypeScript/issues/22156

This revelation is disheartening: even with strict flags enabled (such as strict and strictFunctionTypes), you may end up with error-free code that simply does not function properly due to a type error.

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

Extract the values from HTTP GET request by Id (Observable) and assign them to variables within the component

Hello everyone, it's been a while since I posted on here. Thank you all for your help so far, but I'm facing some difficulties with my Angular+2 web app. I have a User component and a user.service.ts that handles HTTP requests to get user data in ...

Issues with TypeScript bundling external modules

I have a sample TypeScript code that I am attempting to bundle multiple ts/tsx files using the typescript compiler (tsc). Below is the code: File: ISample.ts class ISample{ constructor(public value:string){ } } export = ISamp ...

Learn how to dynamically disable a button based on the input state matching an email pattern!

I'm facing an issue with my login form that has 2 input fields and a login button. One of the input fields requires a valid email pattern. If any of the input fields are left empty, the login button becomes disabled. However, when an incorrect email p ...

Typescript types can inadvertently include unnecessary properties

It seems that when a class is used as a type, only keys of that class should be allowed. However, assigning [Symbol()], [String()], or [Number()] as property keys does not result in an error, allowing incorrect properties to be assigned. An even more curio ...

The feature of getDisplayMedia is not included in TypeScript 3.8

I am currently developing a .Net Core/Angular 8 application in Visual Studio. Within the Angular (ClientApp) section of my project, I have TypeScript 3.5.3 located in node_modules, which includes the following definition in lib.dom.d.ts: interface Navigat ...

Can all objects within an interface be iterated through in order to retrieve both the key and its corresponding value?

I have created an interface that is capable of accepting a variety of search criteria and then passing it to a service that will incorporate those values into the service URL. I am wondering if there is a way to iterate through all the objects in the inter ...

When utilizing AngularFire with Firebase Firestore Database, users may encounter instances where data duplication occurs on

Currently facing a challenge in my Angular 13.1 Project utilizing @angular/fire 7.4.1 for database management The issue arises consistently across various screens where data from Firestore Database is displayed, particularly on List screens. The lists are ...

Distinguishing between TypeScript versions 2.0.x and 2.1.x using type definitions and filtering with a switch/case statement

@ngrx/store (an observable redux implementation for angular (2) ) utilizes a specific pattern to assign the correct type to a reducer. Check out the actual code here. export const ActionTypes = { FOO: type('foo'), BAR: type('bar&apos ...

Storing information in an array based on a specific flag

Currently, I am developing an Angular application where I am dealing with a specific array that contains a flag named "checked". Based on the value of this flag, I need to perform certain manipulations. Here is a snippet of my sample data: const data = [{ ...

Utilizing a setup module for configuration purposes

In the process of developing my angular application, I have integrated several external modules to enhance its functionality. One crucial aspect of my final application is the configuration classes that store important values like URLs and message keys us ...

Restrict the frequency of requests per minute using Supertest

We are utilizing supertest with Typescript to conduct API testing. For certain endpoints such as user registration and password modification, an email address is required for confirmation (containing user confirm token or reset password token). To facilit ...

Creating a ref with Typescript and styled-components: A comprehensive guide

Trying to include a ref into a React component looks like this: const Dashboard: React.FC = () => { const [headerHeight, setHeaderHeight] = useState(0); const headerRef = React.createRef<HTMLInputElement>(); useEffect(() => { // @ts ...

Connecting RxJS Observables with HTTP requests in Angular 2 using TypeScript

Currently on the journey of teaching myself Angular2 and TypeScript after enjoying 4 years of working with AngularJS 1.*. It's been challenging, but I know that breakthrough moment is just around the corner. In my practice app, I've created a ser ...

A guide on applying color from an API response to the border-color property in an Angular application

When I fetch categoryColor from the API Response, I set border-left: 3px solid {{element.categoryColor}} in inline style. Everything is functioning correctly with no development issues; however, in Visual Studio, the file name appears red as shown in the i ...

Initializing ngOnInit and saving the value to an array variable

Currently, I am developing a function that retrieves data from an API. However, the function needs to be called within ngOnInit and the value should be stored in an array variable. MyValue: any; MyValue = MyLocation(); Unfortunately, the MyValue ends up ...

What is causing the issue with TypeScript's React.createRef() and its compatibility with the material-ui Button element?

Running on React version 16.13.1, my component class includes a Material-UI Button component and a RefObject to access the button element. class Search extends React.Component<any, any>{ constructor(props: any) { super(props) this.streetV ...

Utilizing a segment of one interface within another interface is the most effective method

In my current project using nextjs and typescript, I have defined two interfaces as shown below: export interface IAccordion { accordionItems: { id: string | number; title: string | React.ReactElement; content: string | React. ...

Storing references to the DOM elements external to the rendering component

Just diving into the world of Electron + Typescript, so please bear with me. Currently, I'm experimenting with what can be achieved within Electron. Issue: My goal is to manipulate DOM elements outside of the renderer. I pass a button as a parameter ...

Object data is not being received by the defaultValue in React Hook Form

I am currently utilizing React Hook Form to facilitate the process of editing/updating data. I retrieve my data from zustand with a value type of any, and then proceed to save it as the defaultValue in React Hook Form. However, when attempting to acquire v ...

Pausing or buffering an RxJS 6 observable when the page is inactive

Currently, I am dealing with a stream of letters that need to be arranged in the correct order to form a word. However, an issue arises when the user switches tabs, minimizes the browser, or switches applications - the behavior mimics using setTimeout(), r ...