0

I have a function that takes some value, and executes + operator over it and a 2 value:

function myFunction(input: number): number {
    return input + 2;
}

If I pass a number, it will add this number to 2:

const result = myFunction(2);
console.log('Result: ', result);
// Result: 2

If I pass a string, it will concatenate this string to 2:

const result = myFunction('2');
console.log('Result: ', result);
// Result: "22"

Everything good so far. Now I want to use generics in order to capture the type:

function myFunction<T>(input: T): T {
    return input + 2;
}

And if I want to use it capturing the implicit type of the parameter I can do:

const result = myFunction(2);
console.log('Result: ', result);
// Error: `(parameter) input: T. Operator '+' cannot be applied to types 'T' and '2'.`

As you see TypeScript returns an error about the type and the + operator, and I can't understand why. The same if I set the type explicitly:

const result = myFunction<number>(2);
console.log('Result: ', result);
// Error: `(parameter) input: T. Operator '+' cannot be applied to types 'T' and '2'.`

I cant understand why it returns an error with +. Any help will be welcome!

2
  • 1
    perhaps it's answered here: stackoverflow.com/questions/54470329/… Commented Aug 28, 2019 at 7:45
  • 1
    Still can't understand why myFunction<number>(2); is not forcing the type to return input + 2;; TypeScript shouldn't return error in that case! Commented Aug 28, 2019 at 8:31

1 Answer 1

4

In general, TypeScript +operator is more restrictive than JavaScript +operator. Latter and can do implicit conversions between types and is more tolerant in terms of its operands.

Interaction with Generics and functions

Let's take your function example. Given myFunction down under, you get the error as T can be literally anything (see TypeScript + operator section down under for compatible types).

function myFunction<T>(input: T): T {
  // Operator '+' cannot be applied to types 'T' and '2'.  
  return input + 2; 
}

TypeScript also requires, that you narrow down an union type like string | number via control flow analysis.

declare const t: string | number;
t + 3; // Operator '+' cannot be applied to types 'string | number' and '3'.

// this works!
if (typeof t === "string") {
  const res = t + 3; // const res: string
} else {
  const res = t + 3; // const res: number
}

Unfortunately, type narrowing does not work so well with generics extending a union type yet:

function myFunction<T extends string | number>(input: T): string | number {
  if (typeof input === "string") {
    return input + 3;
  } else {
    // TypeScript could not narrow here to number, we have to cast.
    const inputNumber = input as number;
    return inputNumber + 3;
  }
}

So that would be the final version and answer to your question, I guess. As enhancement, a neat thing is to actually return a conditional type. So when we put in string, we want string back. Analogues number -> number. See this Playground example.


Possible combinations for TypeScript +operator

Type matrix for operands (empty spaces means compile error; e.g. between "Other" and "Boolean" type):

+----------+---------+----------+---------+---------+--------+
|          |  Any    | Boolean  | Number  | String  | Other  |
+----------+---------+----------+---------+---------+--------+
| Any      | Any     | Any      | Any     | String  | Any    |
| Boolean  | Any     |          |         | String  |        |
| Number   | Any     |          | Number  | String  |        |
| String   | String  | String   | String  | String  | String |
| Other    | Any     |          |         | String  |        |
+----------+---------+----------+---------+---------+--------+

Excerpt from the specs:

The binary + operator requires both operands to be of the Number primitive type or an enum type, or at least one of the operands to be of type Any or the String primitive type. Operands of an enum type are treated as having the primitive type Number. If one operand is the null or undefined value, it is treated as having the type of the other operand. If both operands are of the Number primitive type, the result is of the Number primitive type. If one or both operands are of the String primitive type, the result is of the String primitive type. Otherwise, the result is of type Any.

Some of the phrases seem a bit outdated. Numeric enums resolve to numbers, but string enums are treated as strings. With null or undefined and number you get a compile error regardless strict settings or not, with string you do a concatenation. Playground


Background info: Javascript +operator

The addition operator produces the sum of numeric operands or string concatenation.

Some constellations, you can only "do" in JavaScript:

true + 1 // 2
false + false // 0
1 + undefined // NaN
1 + null // 1; typeof(1+ null) === "number";   // :)
new Date() + new Date() // toString() is invoked for both Date objects implicitly

Playground

Hope, this wasn't too much text for the scope of your question!

Cheers

Sign up to request clarification or add additional context in comments.

1 Comment

Now I understand, your answer was really really helpful. Tons of thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.