Why TypeScript lets me add a string and a number? Can I prevent it?

Viewed 4249

The following code

let k = "1";
k += 1;
console.log(k);

compiles correctly using TypeScript, even in strict mode. I would have expected tsc to fail with some error like Cannot add a string and an integer.. Why does it build successfuly? Can I prevent this dangerous behavior?

5 Answers

"It's valid because it's valid in JS" is a non-answer in the context of why a certain operation isn't a type error; see What does "all legal JavaScript is legal TypeScript" mean?

In JavaScript, code like alert("Your position in the queue is " + queuePos) is idiomatic and common -- it is not commonly written as "str" + num.toString().

TypeScript's position is that idiomatic JS should not cause type errors (when practical). This means that string + number is an allowed coercion.

The question of what += should do is then a matter of choosing between two options:

  • consistency: x = x + y should be identical to x += y
  • safety: x += y is not commonly done between string and number operands, so should be an illegal coercion

Both choices are sensible and defensible; TypeScript happened to choose the first.

You can prevent this sort of accident with the TypeScript-ESLint rule restrict-plus-operands:

When adding two variables, operands must both be of type number or of type string.

Config examples:

"restrict-plus-operands": true

If you enable that rule, the following code:

const x = 5;
const y = 'foo';
const z = x + y;
console.log(z);

will result in:

ERROR: .ts - Operands of '+' operation must either be both strings or both numbers, but found 5 + "foo". Consider using template literals.

The rule not only prevents use of + between strings and numbers, but it also prevents use of += when the left-hand side type is different from the right-hand side type.

I'm afraid that there is currently no way of preventing such a conversion, aside from building an abstraction of your own. Something like this (admittedly, not very elegant):

function safe_add(x: string, y: string): string { return x + y; }

let x = "1";
x = safe_add(z, 1); // Argument of type '1' is not assignable to parameter of type 'string'.

The usual policy of TypeScript's type checking, according to "All legal JavaScript is legal TypeScript", is to prevent situations which are clearly wrong and never actually useful. For example, passing a string to Math.max is prevented. Unlike this example however, the + operator between a string and a number, despite not always desirable, is indeed a valid operation, and is employed too often in practice to be barred by the compiler. As x += y is equivalent to x = x + y, and always resulting in a string when x is a string, the assignment itself is also valid. This is one of those cases that are most likely going to remain as OK by the compiler. Issue #20131 aims to make a few more operations throw a warning, but this one in particular is not included.

As you might already understand, the opposite is successfully prevented, since a variable is not expected to change its type with the add-assignment operator.

let y = 1;
y += "1"; // Type 'string' is not assignable to type 'number'

See also:

TSLint suggests to use template literals instead of coercion of string and number variables, eg: 'Hello ' + person.name would become `Hello ${person.name}`

In short: because it is valid JS and Typescript as @Explosion Pills already pointed out.

The reason is k += 1 is just syntactic sugar for k = k + 1 which one would expect to be 11. So something like this also works:

let text = "hello ";
text += "world!"
console.log(text); 

Which ends up being hello world!

Related