What is the concern about contravariance in TypeScript interfaces?

Apologies in advance if I am incorrectly using the term, but I have a scenario where I can implement an interface in typescript that may not be entirely type safe. For instance:

interface Point {
  x: number;
  y: number;
  dist (other: Point): number
}

GridPoint implements Point {
  constructor (public x: number, public x: number) {}
  dist (other: Point) { /* ... math here ... */ } //
}

NamedPoint implements Point {
  // this class has an extra `name: string` property...
  constructor (
    public x: number,
    public x: number,
    public name: string
  ) {}

  dist (other: NamedPoint) {
    // other is a NamedPoint so other.name is okay, but this
    // is not true of any Point, so how can NamedPoint be said
    // to implement point?
    if (other.name.startsWith()) { /* ... */ }
  }
}

// this will throw if `a` is a NamedPoint and `b` is a GridPoint
function getDist (a: Point, b: point) {
  console.log(`distance is: ${a.dist(b)}`)
}
// but tsc won't complain here:
getDist(new NamedPoint(1, 2, 'foo'), new GridPoint(9, 8));

link to full example on playground

Once again, I acknowledge that my explanation regarding "contravariance" might be inaccurate, but it seems perplexing that NamedPoint implements Point would not be flagged by the compiler. I initially thought enabling strictFunctionTypes in tsconfig would address this issue, but it seems ineffective in this case.

Is my comprehension of types flawed, or is there a discrepancy in TypeScript logic? If the latter is true, what actions can I take to rectify this?

Answer №1

Your comprehension of types is accurate, however TypeScript deliberately exhibits unsound behavior when it comes to method parameters. It handles methods as being bivariant in their parameter types. This may lead to potential risks, but TypeScript has chosen this approach for reasons related to maintaining certain JavaScript class hierarchies and the convenience of treating types like Array<T> as covariant in T. Refer to "Why Method Bivariance" in the TypeScript FAQ for more details.

Initially, this bivariance applied to all function types, but by enabling the --strictFunctionTypes compiler option, you can restrict this unsoundness to just method types. Therefore, a solution for your issue would involve redefining dist from a method to a function-typed property:

interface Point<T> {
    x: T;
    y: T;
    dist: (other: Point<T>) => T // modify
}

Implementing a function-typed property as a method is still supported, so your GridCoord class will compile without errors. However, with the change, NamedPoint should now display the expected error:

class NamedPoint implements Point<number> {

// ✂ snip ✂ //

    dist(other: NamedPoint): number { // error!
  //~~~~ <-- Type '(other: NamedPoint) => number' is not 
  //         assignable to type '(other: Point<number>) => number'.
        if (other.name.startsWith(this.name)) return 0;
        else return Math.sqrt(
            (this.x - other.x) ** 2 +
            (this.y - other.y) ** 2
        );
    }

}

Access Playground link for 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

Pre-requisites verification in TypeScript

I have a typescript class with various methods for checking variable types. How can I determine which method to use at the beginning of the doProcess() for processing the input? class MyClass { public static arr : any[] = []; // main method public stati ...

Identifying Flaws in Components during Creation with Ready-Made spec.ts Files

Currently, I have embarked on the journey of creating unit tests for my Angular Material project. At the moment, my focus is on testing whether the pre-made spec.ts files for each component are passing successfully. Despite the fact that my project compile ...

Mapping of generic types in typescript

Consider the hypothetical type T: type T = { prop1: (s: S) => T1, prop2: (s: S) => T2, prop3: (s: S) => T3, } Now, let's imagine type W: type W = (s: S) => { prop1: T1, prop2: T2, prop3: T3, } It may be straightforwar ...

What is the best way to transfer information from a child to its parent component?

In my child component, I have a variable containing the array result of a Barcode Scanner that I need to pass to the parent page. This array needs to be sent to an API. childComponent.ts this.consultList; parentComponent.ts export class ParentComponent i ...

Contrast the equality of two arrays with objects

I have two different types of data structures var dataA = [ { "Employee Name": "Mr. X", id: "1" }, { "Employee Name": "Mr. Y", id: "2" }, { "Employee Name": "Mr. Z", id: "3" } ]; var dataB = [ { id: "1", " ...

Minimizing assets in Angular 7 by running the command ng build --prod

After running ng build --prod, the JavaScript and CSS files located in /assets are not being minified. Is there a way to minify these files? I've checked the angular documentation but couldn't find any relevant information. ...

Error message: In the combination of NextJs and Redux, an issue has occurred where the program is unable to access properties of null, specifically in

I am just getting started with Next and redux, but I am facing an issue. https://i.sstatic.net/CZTO2.png The error shown above occurs when trying to select a redux value from the store. I have attempted using raw useSelector from redux toolkit, but it s ...

Automatically adjust the model input (Signal) based on the parent and respond to any alterations in the children

Within my Angular 16 application, I have a parent component that passes a plain JavaScript object (myObj) to a child component, where it is treated as a model<MyObj>. <!-- parent.component.html --> <app-children [myObjModel]="myObj&qu ...

Using scrollIntoView() in combination with Angular Material's Mat-Menu-Item does not produce the desired result

I am facing an issue with angular material and scrollIntoView({ behavior: 'smooth', block: 'start' }). My goal is to click on a mat-menu-item, which is inside an item in a mat-table, and scroll to a specific HTML tag This is my target ...

Exploring the World of Typescript Interfaces

I have an interface defined in my code that looks like this. interface MyFlag { flag1: boolean, flag2: boolean } In my code, I initialize the interface like this. let myFlag: MyFlag = {"flag1":true, "flag2": true}; let dummy = myFlag; console.lo ...

Including .d.ts files in TypeScript files

I am facing an issue with importing types from a .d.ts file that I created. The TypeScript compiler is giving an error related to the file path, displaying this message: File '.../lib/types/generated.d.ts' is not a module.ts(2306) The error occu ...

Leveraging Leaflet or any JavaScript library alongside Typescript and webpack for enhanced functionality

Important: Despite extensive searching, I have been unable to find a resolution to my issue. My current endeavor involves developing a map library through the extension of leaflet. However, it appears that I am encountering difficulties with utilizing js ...

The Cypress-TinyMCE package consistently returns undefined for the editor instance when using TypeScript

My current project involves building a React JS application with TypeScript, where I utilize the TinyMCE editor within a form. To further enhance my development process, I am incorporating integration tests using Cypress in TypeScript. However, I have enco ...

Http' does not have the 'update' property

I recently implemented Angular 2 Release and utilized 'Http' from '@angular/http' for my project. However, I encountered an error when I invoked the method 'update', which resulted in the following message: "Evidently, th ...

Looking for a top-notch type definition management solution for Typescript, similar to tsd?

When considering the use of Typescript, the resolution of type definition files (*.d.ts) is essential. There are various systems for managing Typescript definition files, including: tsd typings @types It seems that tsd is the oldest system and the orig ...

Developing a Angular 2.3 custom library with advanced dependency injection techniques

I am currently facing a challenge in integrating a custom service from a Yeoman-created library into my existing Ionic2 project. The index.ts file of the library, which will be installed as an npm module, is structured as follows: @NgModule({ imports: ...

An issue has occurred: TypeError - the data is not a function

I am attempting to pass the alpha2code variable through another component so that when a row in my table is clicked, it should display data specific to the alpha2code. This is the function that is triggered when a specific row in the table is clicked: on ...

The error message "Invalid export value for Next.js with TypeScript: 'getStaticProps'" indicates a problem with the entry export value

I'm currently diving into the world of Next.js and Typescript, and I've run into an error that has left me stumped as there doesn't seem to be much information available on it: "getStaticProps" is not a valid Next.js entry export value.ts( ...

Verifying TypeScript errors before each commit in a Vue application

We have set up a git hook in our app using Husky for pre-commit actions. Whenever someone commits code, it triggers the pre-commit code - #!/bin/sh . "$(dirname "$0")/_/husky.sh" export NVM_DIR="$HOME/.nvm" [ -s "$NVM_ ...

Setting default values and specifying available values for a class property in TypeScript

I'm curious about how to set a default value for a class property, as well as define all available values at once. For example: class MyClass{ isActive = -1; //Setting default value } class MyClass{ isActive: -1 | 0 | 1; //Defining all available ...