Working with undefined covariance in TypeScript

Despite enabling strict, strictNullChecks, and strictFunctionTypes in TypeScript, the following code remains error-free. It seems that TypeScript is not catching the issue, even though it appears to be incorrectly typed.

abstract class A {
    // You can pass an undefined to any A
    public abstract foo(_x: number | undefined);
}

class B extends A {
    // B is an A, but it prohibits passing in an undefined.
    // Note that if we did `x: string`, TS would flag it as
    // an error.
    public foo(x: number) {
        if (x === undefined) {
            throw new Error("Type error!");
        }
    }
}

function makeAnA(): A {
    // This typechecks correct, so B is clearly an A, in
    // TS's opinion.
    return new B();
}

function test() {
    const b = makeAnA();
    // b is a B, so this should not be possible
    b.foo(undefined);
}

Does this behavior align with expectations? Is there an option available to highlight this discrepancy as an error? This issue has caused problems more than once.

Answer №1

The approach taken here reflects a deliberate design choice. In this context, all method parameters exhibit bivariant behavior. This essentially means that within TypeScript, a method like (_x: number) => void is considered a subtype of (_x: number | number) => void (and vice versa). However, it's evident that this approach lacks robustness.

Initially, not only did method parameters showcase bivariance, but all function signature parameters did as well. To address this issue, the strictFunctionTypes flag was introduced in typescript 2.6. As stated in the PR:

This PR introduces a --strictFunctionTypes mode in which function type parameter positions are examined contravariantly rather than bivariantly. This stricter checking rule applies to all function types except those originating from method or constructor declarations. Methods are exempted specifically to ensure generic classes and interfaces (such as Array) remain predominantly covariant. Strict method checking would bring about a significant breaking change due to a large number of generic types becoming invariant. Nevertheless, exploring this stricter mode may still be on the cards.

(highlight added)

Hence, the decision to maintain bivariant relationships for method parameters can be attributed to convenience. Without this leniency, most classes would end up being invariant. For instance, if Array were invariant, Array<Dog> would not qualify as a subtype of Array<Animal>, leading to numerous challenges in basic code implementation.

Although not identical, when utilizing a function field instead of a method (alongside strictFunctionTypes enabled), an error is triggered stating

Type '(x: number) > void' is not assignable to type '(_x: number | undefined) > void'

abstract class A {
    // Capability to accept undefined values for any instance of A
    public foo!: (_x: number | undefined) => void;
}

class B extends A {
    // Error detected at this point
    public foo: (x: number) => void = x => {
        if (x === undefined) {
            throw new Error("Type error!");
        }
    }
}

function createAnInstanceA(): A {
    // And over here 
    return new B();
}

function verify() {
    const b = makeAnA();
    // Since b represents a B object, this operation should not be valid
    b.foo(undefined);
}

Playground Link

Note: The code snippet above generates an error exclusively with strictFunctionTypes enabled, as without it, all function parameters continue to behave bivariantly.

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

Simple guide on implementing React with Typescript to support both a basic set of properties and an expanded one

I'm grappling with a React wrapper component that dynamically renders specific components based on the "variant" prop. Despite my efforts, I cannot seem to get the union type working properly. Here's a snippet of the code for the wrapper componen ...

Module or its corresponding type declarations not found in the specified location.ts(2307)

After creating my own npm package at https://www.npmjs.com/package/leon-theme?activeTab=code, I proceeded to set up a basic create-react-app project at https://github.com/leongaban/test-project. In the src/index.tsx file of my react app, I attempted to im ...

Angular's interactive checkboxes and dropdown menus provide a dynamic user experience

There is a global List array where data from an API is passed in the OnInit method. List: any; visibility:any; Status:any; ngOnInit(): void { let param = {...}; this.Service.getUser(param).subscribe(result => { this.List = result['response ...

Utilize Hardhat and NPM to distinguish between unit tests and integration tests efficiently

Struggling with setting up two commands in my package.json for running unit tests and integration tests. I am having trouble defining the location of the testset for each type of testing. Within the scripts section of my package.json, I have created two c ...

Rect cannot be resized using mouse events

I am currently working on resizing the rectangle inside the SVG using mouse events. To achieve this, I have created another circle shape at the right bottom edge of the rectangle and implemented resize events on that shape. However, I'm facing an issu ...

The error message "Error: cannot read property ‘setDirtyAttribute’ of null" may be encountered when attempting to use the method YourModel.create({...}) in ember-typescript-cli

Encountering the error cannot read property 'setDirtyAttribute' of null even when using YourModel.create({...}) in ember-typescript-cli to instantiate an EmberObject. Model: import DS from 'ember-data'; import {computed} from "@ember/ ...

Typescript service wrapper class returning Axios HEAD request

I am attempting to retrieve the header response using a custom Axios HTTP service wrapper. axiosClass.ts import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; class Http { private instance: AxiosInstance | null = n ...

Unique loading animations are assigned to each individual page within the Next.js framework

Is there a way to have unique loading animations for each of my website pages during the loading process? How can I achieve this? I've attempted to put the loading component on the page component directly, but it doesn't seem to work: //Page com ...

What is the reason for the index type being defined twice?

Here is an example from the official TypeScript documentation: class Animal { name: string; } class Dog extends Animal { breed: string; } // Error: indexing with a 'string' will sometimes get you a Dog! interface NotOkay { [x: numbe ...

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) => { // ...

Steps for transitioning a VUE JS project to TypeScript

Is it possible to transition a VUE JS project from JavaScript to TypeScript without rewriting everything? I heard from a friend that it can be done through the VUE CLI, but I haven't been able to find any documentation or articles on this method. Has ...

What is the best way to pass a generic interface to the zustand create function in a TypeScript environment

Having trouble figuring out the right syntax to pass a generic interface when calling a function that accepts a generic type. My goal is to use: const data = itemStore<T>(state => state.data) import { create } from "zustand"; interface ...

The custom validation in nestjs is throwing an error due to an undefined entity manager

I've been working on developing a custom validation for ensuring unique values in all tables, but I encountered this error: ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'getRepository') TypeError: Cannot read proper ...

The Express application fails to receive a response from a Mongodb query function

In my current project, I am implementing a simple API Key authentication system. The main goal is to validate the provided key against the user's input. There is a separate file containing a function that queries the database and returns either true/ ...

Creating a type-safe method wrapper in TypeScript based on function names

Many Q&As discuss creating a function wrapper in TypeScript, but my question is how to do the same with named methods. I am interested in writing something similar to this JavaScript code: function wrap(API, fnName, fn) { const origFn = API.p ...

ngx-emoji mart - The error message "Type 'string' is not assignable" is being displayed

While working on a project involving the @ctrl/ngx-emoji-mart package, I encountered a perplexing issue. The code functioned flawlessly in Stackblitz but when I attempted to run it on my local system, an error surfaced: Type 'string' is not assig ...

Error: Model function not defined as a constructor in TypeScript, mongoose, and express

Can anyone help me with this error message "TypeError: PartyModel is not a constructor"? I've tried some solutions, but now I'm getting another error as well. After using const { ... } = require("./model/..."), I'm seeing "TypeError: C ...

Find all Mondays occurring within a specified date range using Moment.js

I need to extract all Mondays within a specific date range. let start = moment(this.absence.FromDate); let end = moment(this.absence.ToDate); The user has the option to deactivate certain weekdays during this period by setting booleans. monday = true; t ...

The application's functionality is interrupted when router.navigate() is called within the .subscribe method

I am having an issue with user navigation on my application. After successfully signing in, users get redirected to the home page (/), but they are unable to navigate by clicking any links on that page. Upon further investigation, I discovered that moving ...

Conceal a designated column within a material angular data table based on the condition of a variable

In the morning, I have a question about working with data tables and API consumption. I need to hide a specific column in the table based on a variable value obtained during authentication. Can you suggest a method to achieve this? Here is a snippet of my ...