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

Embed the div within images of varying widths

I need help positioning a div in the bottom right corner of all images, regardless of their width. The issue I am facing is that when an image takes up 100% width, the div ends up in the center. How can I ensure the div stays in the lower right corner eve ...

Interface-derived properties

One of the challenges I'm facing is dealing with a time interval encapsulation interface in TypeScript: export interface TimeBased { start_time: Date; end_time: Date; duration_in_hours: number; } To implement this interface, I've created ...

Utilize ngClass for every individual section

I have completed the implementation of all UI components, which are visually appealing. Here is the data structure I am using: public filters = [ { tag: 'Year', label: 'year', items: [200 ...

Encountering a 404 error when trying to deploy an Angular application

After following the Angular documentation for deployment, I am deploying my angular application on github pages. The steps I have taken include: 1. Running "ng build --prod --output-path docs --base-href /<project_name>/". 2. Making a copy of docs/ ...

Issue encountered when transitioning from Angular 8 to Angular 9: Value discrepancy at index 0

While updating my Angular project from version 8 to 9, I encountered an issue after following the update guide on update.angular.io and running npm update. When attempting to compile the website using ng serve, the following error occurred: ERROR in Faile ...

TypeScript requires that when explicitly specifying the return type of a generator, the `Generator.prototype.return` function must accept

I am utilizing a generator function to serve as a consumer for accumulating strings: function *concatStrings(): Generator<void, string, string> { let result = ''; try { while (true) { const data = yield; result += data; ...

Using ngIf for various types of keys within a JavaScript array

concerts: [ { key: "field_concerts_time", lbl: "Date" }, { key: ["field_concert_fromtime", "field_concert_totime"], lbl: "Time", concat: "to" }, { key: "field_concerts_agereq", lbl: "Age R ...

Tips on creating a unit test for validating errors with checkboxes in your code base

In a certain scenario, I need to display an error message when a user clicks on the Next button without agreeing to the terms. To achieve this, I am looking to write a unit test case using Jest and React Testing Library. How can I go about doing this? im ...

What is the best way to change multiple parameters using ngModelChange?

I have a requirement in my application to update 3 values using a single ngModelChange event. The component structure is as follows: model: any = {}; images: any; public input = true; public dropdown = false; images : any; constructor(...services) { } ...

React dynamic table

I've been experimenting with creating a dynamic table in React that allows users to add and delete rows. I need the data entered by the user to be saved, possibly using in-state management so that I can work with it later. Essentially, I'm looki ...

Node.js is known for automatically adding double quotes to strings. However, is there a way to eliminate them?

After creating a unit test, I noticed that both logics I used led to the same issue. Logic 1: describe('aresFileCopier', () => { test('log error', async () => { await registerDB('ares-test', { client: ' ...

The attribute 'X' is not present in the specified type 'IntrinsicAttributes & InferPropsInner'

I've been restructuring my code from a .js file to a .tsx file, as seen below: import React, { useEffect, useState } from 'react' import PropTypes from 'prop-types' import { checkMobile } from '../../utils/common' import ...

Learn how to manipulate Lit-Element TypeScript property decorators by extracting values from index.html custom elements

I've been having some trouble trying to override a predefined property in lit-element. Using Typescript, I set the value of the property using a decorator in the custom element, but when I attempt to override it by setting a different attribute in the ...

Tips for obtaining the width of a child element during a resize event in an Angular application

When resizing the window, I am attempting to determine the width of a specific sub-component. If I want to retrieve the entire app's width, I can use the following code: @HostListener('window:resize', ['$event']) onResize( ...

Ways to merge two distinct arrays [Angular and Typescript]

I am faced with a task where I need to merge two array lists in order to create a new array list that contains all the values associated with a common UUID in both lists. The final list should include all the values linked to the UUID in each of the origin ...

Why is it that in Angular, console.log(11) is displayed before console.log(1)?

Can someone help me understand why my simple submit method is printing Console.log(11) before Console.log(1)? I'm confused about the order of execution. submit(value) { this.secServise.getUserById(this.currentUser.mgId).subscribe( uAddrs => { ...

Ways to modify the CSS of an active class within a child component when clicking on another shared component in angular

In my HTML template, I am encountering an issue with two common components. When I click on the app-header link, its active class is applied. However, when I proceed to click on the side navbar's link, its active class also gets applied. I want to en ...

What are the steps to set up NextJS 12.2 with SWC, Jest, Eslint, and Typescript for optimal configuration?

Having trouble resolving an error with Next/Babel in Jest files while using VSCode. Any suggestions on how to fix this? I am currently working with NextJS and SWC, and I have "extends": "next" set in my .eslintrc file. Error message: Parsing error - Can ...

Guide to accessing a nested and potentially optional object property with a default value and specifying its data type

Just a simple query here... my goal is to extract data.user.roles, but there's a possibility that data may be empty. In such cases, I want an empty array as the output. Additionally, I need to specify the type of user - which in this instance is any. ...

"Encountering issues with DefinePlugin when using the combination of Ionic, Angular, Webpack,

I'm trying to incorporate my process.env variable into the webpack Bundle using DefinePlugin. Here's the snippet of code in my webpack config: plugins: [ new webpack.DefinePlugin({ 'process.env': JSON.stringify(process.env) ...