How is it possible that only certain type combinations pass while functions with parameters of union type fail?

const add = (x:string|number) => x+x

results in a TypeScript error:

Operator '+' is not allowed for types 'string | number' and 'string | number'.ts(2365)

However, when the types are separated like this:

const add = (x:string) => x+x
const add = (x:number) => x+x

they pass type checking without any errors. Why is that?

Answer №1

It seems that the idea of adding two string | number operands to yield a string | number output is up for debate. The question arises as to whether the binary + operator should extend its functionality to handle unions in the types of its operands.

From my investigation, the current behavior appears intentional but could be considered either a design limitation or a missing feature. While some numeric operators in TypeScript may not align perfectly with user expectations, the issues raised by such use cases do not seem significant enough to warrant immediate attention. In the case of combining two string | number operands with +, there doesn't appear to be strong demand for explicit support.

As far as I can tell, no GitHub issues—open or closed—exist requesting this specific type of support.


It's worth noting that TypeScript deliberately flags certain valid JavaScript code as invalid. Check out What does "all legal JavaScript is legal TypeScript" mean? for further explanation on this point. Merely stating that code works in JavaScript is not sufficient justification for allowing it in TypeScript; there needs to be evidence that the code is intentional and not a bug.

The need to add together two string | number values might not be a common requirement; string concatenation and numerical addition are distinct operations facilitated by the same + operator. It can be viewed as an overloaded function. TypeScript restricts calling overloaded functions using unions involving parameter types from multiple signatures; refer to microsoft/TypeScript#14107 for a feature request related to this behavior.

An open issue advocating for supporting the addition of two string | number values may face limited traction. A similar issue regarding the inability to add two values of type Number remains unresolved (see microsoft/TypeScript#2031). This suggests that accommodating non-conventional cases like Number has been proposed but neglected over time.

Ultimately, this matter does not appear critical to the TypeScript team's priorities.


To address this limitation independently, one workaround involves:

const add = (a: string | number, b: string | number) =>
  ((a as any) + b) as (string | number);

This approach consistently yields a string | number output. Alternatively, you can explore a distributive conditional generic solution, such as the following:

const add = <T extends string | number, U extends string | number>(t: T, u: U) =>
  ((t as any) + u) as (T extends number ? U extends number ? number : string : string);

const five = add(2, 3); // number
const ab = add("a", "b"); // string
const aThree = add("a", 3); // string
const twoB = add(2, "b"); // string
const alsoTwoB = add(Math.random() < 0.5 ? "2" : 2, "b"); // string
const roulette = add(Math.random() < 0.5 ? "2" : 2, 3); // string | number

This function produces narrow output types based on the inputs' respective types.

Access the Playground link here

Answer №2

According to the response provided by Jcalz.

In order to grasp this concept, we must pay close attention to the + operator.

Though the operator does not have a clear signature, the Typescript spec specifies the types that can be used on each side.

https://i.sstatic.net/mHjbn.png

Let's consider the number and string parameter types. We can view the plus operator as having these function overloads:

function plusOperator(left: number, right: number): number
function plusOperator(left: number, right: string): string
function plusOperator(left: string, right: number): string
function plusOperator(left: string, right: string): string

Currently, Typescript does not allow us to pass string|number to the aforementioned pluseOperator function, even though we are aware that the function is capable of handling it. This has sparked some controversy, and a feature request to change this behavior is still pending (refer to issue 14107).

Answer №3

In comparing your initial function (using union types) to the following three functions, a clear distinction arises: the subsequent three functions consistently either perform numeric addition or string concatenation. On the contrary, the first function with union types exhibits variability by sometimes executing numeric addition and other times handling string concatenation.

Rarely would one intentionally write code utilizing the + operator in a manner that allows for both numeric addition and string concatenation based on the data types at runtime. Consequently, if the behavior of switching between the two operations occurs, it is likely unintended by the programmer. In such cases, it is justifiable for a compiler to flag this as an error.

If there is a deliberate intention behind using the + operator to switch between numeric addition and string concatenation depending on the types at runtime, then utilizing a @ts-ignore directive could serve as a valid approach:

function add(a: string | number, b: string | number): string | number {
    // @ts-ignore Intentionally ambiguous use of + operator
    return a + b;
}

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

A guide to showcasing a series of strings in a react element

My aim is to present an array of strings on distinct lines within my React component by utilizing the following code: <div> {this.props.notifications.join('\n')} </div> Nonetheless, it appears that this a ...

Exploring the versatility of Highcharts with callback functions and comprehensive

Currently, I am utilizing Highcharts with angular 9. Here is the link to the code : https://stackblitz.com/edit/angular-ivy-wdejxk For running on the application side or test side, follow these instructions: Test Side: In the angular.json file at line ...

Unable to set up enzyme adapter

Currently, I am in the process of setting up the enzyme adapter for testing purposes. The code snippet that I have is quite straightforward: import * as enzyme from 'enzyme'; import * as Adapter from 'enzyme-adapter-react-16'; enzyme. ...

Confirm button title by verifying part of the label that contains a space

I'm facing an issue with clicking a button using the following code: await page.getByRole('button', { name: '3 Employees' }).click(); The problem is that the button's name fluctuates based on the number of employees, causing ...

The data type 'string' does not share any properties with the type 'Properties<ReactText, string & {}>'

My function react element is structured as follows: import { CSSProperties } from 'styled-components' export interface StylesDictionary { [Key: string]: CSSProperties } interface Props { onClick: () => void buttonStyle?: StylesDictionary ...

Enhancing JSON data: Transforming basic JSON structure into more complex format

I am currently working on a typescript file that is receiving a JSON response from an external API. I am in need of assistance to convert the received JSON into a different format. Could someone please help me with this JSON conversion task? Sample JSON d ...

What causes Angular guards to execute before the ngOnInit lifecycle method?

In my application, I have a function called checkAuth within the ngOnInit method of the app.component.ts file. This function checks the localStorage for a token and if the token is valid, the authService logs you in. Additionally, there is an AuthGuard se ...

Generating RSA Public Key with JavaScript or TypeScript

I am facing an issue with my c# web api and React app integration. The problem arises when trying to reconstruct a public key in the frontend for encryption purposes. Despite generating public and private keys in c#, I am struggling to properly utilize th ...

The error occurred in Commands.ts for Cypress, stating that the argument '"login"' cannot be assigned to the parameter of type 'keyof Chainable<any>))`

Attempting to simplify repetitive actions by utilizing commands.ts, such as requesting email and password. However, upon trying to implement this, I encounter an error for the login (Argument of type '"login"' is not assignable to parameter of t ...

I am interested in utilizing Template literal types to symbolize placeholders

Currently, I am in the process of converting existing javascript files into typescript for my business needs. Below is an example object structure: [ { // Sample column names givenName, familyName, and picture are provided as examples. "giv ...

A step-by-step guide to resolving the 'Type is not assignable error' in Typescript when dealing with an array of elements

Struggling to make this work, facing the same error repeatedly. Since I am still new to Typescript, a breakdown of what this error message signifies would be greatly appreciated: const itemsRef = useRef<Array<HTMLLIElement | null>>([]); ... & ...

How to transform Observable<string[]> to Observable<string> using RxJS

Currently, I am facing the task of converting an Observable<string[]> to an Observable<string> I'm uncertain whether the mergeAll operator is the solution for this scenario. const o1: Observable<string[]> = of(['a', &apos ...

When working with Typescript, an error is thrown if property "p" does not exist on one of the classes that are OR

In my component class, I have a property called renderContent which can be of either LessonPageType or TaskPageType based on the input value. Below is the code snippet from my component: import {ChangeDetectionStrategy, Component, HostListener, Input, OnI ...

Is there a way to attach a ref to a Box component in material-ui using Typescript and React?

What is the best way to attach a ref to a material-ui Box component in React, while using TypeScript? It's crucial for enabling animation libraries such as GreenSock / GSAP to animate elements. According to material-ui documentation, using the itemRef ...

Building Interactive Graphs with Chart.JS in Angular Using Observables

Having some difficulty setting up a Chart with Angular and Chart.JS. I'm struggling to pass my Observable data into the Chart, no matter what I try. error TS2339: Property 'cool_data' does not exist on type 'Observable<Object>&a ...

I'm struggling to figure out the age calculation in my Angular 2 code. Every time I try to run it,

Can anyone assist me with calculating age from a given date in Angular? I have written the following code but I keep getting undefined in the expected field. This is Component 1 import { Component, OnInit, Input } from '@angular/core'; ...

Using Typescript, you can specify an array of type <T> within an object

Having a background in JS, I am currently exploring TS. My goal is to create an object with a single field, which is an array of strings, while ensuring strong typing. let container = { items: ['Item 1'] } container.items[0] = 3; // This is i ...

Toggling multiple ions simultaneously does not function independently

I encountered a problem while working on an ionic app. I needed to have individual control over toggle switches, but my current code toggles all switches at once whenever one switch is tapped. Is there a way to fix this and manage each toggle switch separa ...

How do you obtain the string name of an unknown object type?

In my backend controllers, I have a common provider that I use extensively. It's structured like this: @Injectable() export class CommonMasterdataProvider<T> { private readonly route:string = '/api/'; constructor(private http ...

Issue: Invalid parameter: 'undefined is not a numeric value' for DecimalPipe

Here is the HTML input code that I am using: <input class="number " type= "text" pInputText [readonly]="" formControlName="id" [required]="" plmNumberFormatter [value]="data?.id | numberPipe" /> However, when I place the cursor on the input fiel ...