You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
No warning when using unary +/- operators on functions and objects? #20131
letfunc=()=>"func";+func;// no error-func;// no errorletobj={foo: "bar"};+obj;// no error-obj;// no error
Expected behavior:
Unary + or - is only applicable to a number or maybe a string. I would expect the lines listed as // no error to be errors.
Actual behavior:
Unary + and - never generates a warning no matter what crazy stuff I try to apply it to.
I was made aware of this behavior by a recent Stack Overflow question. I see from #7019 that unary + and - on strings is supported, and I guess I can understand that as a way to parse a string into a number (for +, anyway. Is - really used like this in practice?). But functions? objects? Seems more likely to be an error than intentional to me. Thoughts?
It may not make much sense for anyone to ever use + or - on a non-primitive value, but it is not incorrect code. It will execute just fine, converting to a string, and then to a number (usually NaN). There is one use case I could could imagine: +[x], where x is a number or string, evaluates to +x. The + operator is equivalent to calling Number() on the value, and the signature of that function is (value?: any) => number, so + and - can technically be applied to any value.
Because JavaScript tries to make sense of a lot of operations that would cause other languages to throw an error, you could argue that TypeScript should prevent (or warn) you for many valid operations. For example, adding a string to a number? 1 + 2 + '3' similarly seems like a "crazy" application of the binary + operator, but it is perfectly valid. What about x / 0?
I think the litmus test isn't usually "is this code invalid JavaScript" but "is this code likely to be a bug" (and whether the false positives you would get by flagging it as an error are preferable to the false negatives you would get by allowing it). JavaScript allows almost everything. TypeScript should warn you when you are doing something allowable-but-probably-wrong.
This issue is asking whether people really use unary + or - on non-string and non-number operands in practice enough to warrant supporting it. Who do we break by disallowing it, and should they be left alone or told to make their code more explicit by using +String(x) or -(x.toString()) or Number(x) when x is a function or an object?
Reacted by Gigih Aji Ibrahim, Aluan Haddad, methodbox and Flavio Vilante
Seems wrong when we can be reasonably confident that the result will be NaN. Operands should be of type number, string, Date, or boolean (am I missing anything?)
If we intentionally disallow the single-element tuple and objects with valueOf() of toString() overrides, how much real-world code does that break? I obviously can't speak for everyone but I'd be happier being alerted for all of these:
We agreed that the current behavior is dumb, but would prefer to group this change into an overall revamp of how the general numeric operators (especially > and <) work. The TL;DR of that is that we need the type system to special-case Date, both because of its idiomatic usage (+someDate is common) and wacky behavior (its preference of coercion to string vs number is different from all other objects)
Reacted by Joe Calzaretta, Sean M. Vieira and Jakub Korzeniowski
I just came across this recently. I declared some strings across multiple lines, concatenated with +, and in one place accidentally put a + at the end of one line and the start of the next. I was very surprised that it got through type checking.
My feeling is that the unary +/- operators are a special case. I think semantically they really shouldn't be operators at all, but rather part of a numeric literal. I suspect they were only made into operators in the first place to make parsing easier.
Parsing unary operators as operators is actually harder than treating them as part of the literal. However, if they weren't operators you wouldn't have been able to write let y = -x;.
How about supporting a declaration that a type supports the unary operator, then if someone designs a type meant to be used like that, it can be. Same could be said for binary operators I guess.
Unary could be associated with the type directly:
typeHasUnaryPlus={+: number;};constx: HasUnaryPlus=aquireTheThing();console.log(+x);// OK
Binary operators would need to be declared separate from the types:
typeA= ... ;declareoperator+(a0: A,a2: A): A;
Could be useful for e.g. moment:
moment(moment()+moment.duration(3,'days'));
The code makes sense, and runs fine as javascript, but does not validate as typescript.
It seems there is already a concept in TypeScript of what is "numeric," and I don't think any of the concerns about idiomatic uses of unary + apply to unary -.
I just ran into a bug in my program where I wrote -foo.bar instead of -foo.bar.value. Needless to say, foo.bar is not numeric. If I had written 0 - foo.bar, TypeScript would have flagged an error.
I find it hilarious (in a morbid sort of way) that implicitly converting a function to a string to a number is perfectly fine, but I can't even use an object with a toString method as a computed key.
constwtf=-(()=>"func")// you do you, javascript developer!conststringableFn=()=>{};stringableFn.toString=()=>"I'm a key!";constslowDownThereBuddy={[stringableFn]: 1// A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)}
This annoys the hell out of me since this is a common pattern with many redux action creators libraries. I have a hard time believing that the former example is considered most likely intentional, and the latter is considered most likely a mistake.
I just want to point out that +object is a perfectly valid expression when using math libraries with custom numeric types (like fraction.js and decimal.js used by math.js).
Activity
calebsander commentedon Nov 18, 2017
It may not make much sense for anyone to ever use
+or-on a non-primitive value, but it is not incorrect code. It will execute just fine, converting to a string, and then to a number (usuallyNaN). There is one use case I could could imagine:+[x], wherexis a number or string, evaluates to+x. The+operator is equivalent to callingNumber()on the value, and the signature of that function is(value?: any) => number, so+and-can technically be applied to any value.Because JavaScript tries to make sense of a lot of operations that would cause other languages to throw an error, you could argue that TypeScript should prevent (or warn) you for many valid operations. For example, adding a string to a number?
1 + 2 + '3'similarly seems like a "crazy" application of the binary+operator, but it is perfectly valid. What aboutx / 0?jcalz commentedon Nov 18, 2017
Relevant reading about "not incorrect code" by Ryan Cavanaugh (@RyanCavanaugh):
isNaNandisFiniteshould accept number or stringI think the litmus test isn't usually "is this code invalid JavaScript" but "is this code likely to be a bug" (and whether the false positives you would get by flagging it as an error are preferable to the false negatives you would get by allowing it). JavaScript allows almost everything. TypeScript should warn you when you are doing something allowable-but-probably-wrong.
This issue is asking whether people really use unary
+or-on non-stringand non-numberoperands in practice enough to warrant supporting it. Who do we break by disallowing it, and should they be left alone or told to make their code more explicit by using+String(x)or-(x.toString())orNumber(x)whenxis a function or an object?RyanCavanaugh commentedon Nov 18, 2017
Seems wrong when we can be reasonably confident that the result will be
NaN. Operands should be of typenumber,string,Date, orboolean(am I missing anything?)sean-vieira commentedon Nov 20, 2017
As Caleb Sander Mateos (@calebsander) points out, the single element tuple when it's value is
number | string.Also, any object that overrides
Object.valueOfand returns either a string or a number:or
toStringand returns something that is a stringified number:jcalz commentedon Nov 20, 2017
If we intentionally disallow the single-element tuple and objects with
valueOf()oftoString()overrides, how much real-world code does that break? I obviously can't speak for everyone but I'd be happier being alerted for all of these:so I could either rewrite them or assert the quantity in parentheses as
anyto force the type check to succeed:Hey, did you realize that
zwas actuallyNaNinstead of3? Neither did I.RyanCavanaugh commentedon Nov 20, 2017
I'm really skeptical that anyone uses
-to pluck a coerced number out of a single-element array. Or+for that matter.RyanCavanaugh commentedon Nov 28, 2017
We agreed that the current behavior is dumb, but would prefer to group this change into an overall revamp of how the general numeric operators (especially
>and<) work. The TL;DR of that is that we need the type system to special-case Date, both because of its idiomatic usage (+someDateis common) and wacky behavior (its preference of coercion to string vs number is different from all other objects)james-cnz commentedon Jan 25, 2018
I just came across this recently. I declared some strings across multiple lines, concatenated with +, and in one place accidentally put a + at the end of one line and the start of the next. I was very surprised that it got through type checking.
My feeling is that the unary +/- operators are a special case. I think semantically they really shouldn't be operators at all, but rather part of a numeric literal. I suspect they were only made into operators in the first place to make parsing easier.
gcnew commentedon Jan 25, 2018
Parsing unary operators as operators is actually harder than treating them as part of the literal. However, if they weren't operators you wouldn't have been able to write
let y = -x;.markusjohnsson commentedon Feb 28, 2018
How about supporting a declaration that a type supports the unary operator, then if someone designs a type meant to be used like that, it can be. Same could be said for binary operators I guess.
Unary could be associated with the type directly:
Binary operators would need to be declared separate from the types:
Could be useful for e.g. moment:
The code makes sense, and runs fine as javascript, but does not validate as typescript.
dgreensp commentedon May 15, 2018
Could you make
-xfollow the same rules as0 - x?It seems there is already a concept in TypeScript of what is "numeric," and I don't think any of the concerns about idiomatic uses of unary
+apply to unary-.I just ran into a bug in my program where I wrote
-foo.barinstead of-foo.bar.value. Needless to say,foo.baris not numeric. If I had written0 - foo.bar, TypeScript would have flagged an error.methodbox commentedon Jun 18, 2019
Should unary operators even be allowed in TS?
parkerault commentedon Aug 15, 2019
I find it hilarious (in a morbid sort of way) that implicitly converting a function to a string to a number is perfectly fine, but I can't even use an object with a
toStringmethod as a computed key.This annoys the hell out of me since this is a common pattern with many redux action creators libraries. I have a hard time believing that the former example is considered most likely intentional, and the latter is considered most likely a mistake.
cshaa commentedon May 27, 2020
I just want to point out that
+objectis a perfectly valid expression when using math libraries with custom numeric types (like fraction.js and decimal.js used by math.js).Globally disallowing the unary plus for objects sounds like a very bad idea here.
pbadenski commentedon Mar 20, 2021
Is there lack of appetite to altering this behavior? Just wondering - I got redirected here from eslint github page: typescript-eslint/typescript-eslint#3200
If it's not coming soon in TS, I'm wondering if it's a good idea to implement an eslint rule for this?