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
functioncompare(a: number|string,b: number|string): boolean{returna<b;// <- Fails to error}compare(1,"a");1<"a";// Correctly errors: Operator '<' cannot be applied to types 'number' and 'string'.
This is a simplified example taken from an error found in code review. The function's type signature should really be: function compare<T extends number | string>(a: T, b: T): boolean and I would have expected TypeScript to error for the uncorrected type signature.
Expected behavior:
Typescript should catch the error comparing union types within the function as Flow (v0.136.0) does: flow.org/try example.
Are you actually talking about the generic case or the non-generic case? I'm inclined to agree about the non-generic case but I think erroring on the generic version would be a bridge too far for a lot of users.
For the unbounded generic case in #5156 (comment), I feel the most ergonomic outcome would be to defer the type checking of the unbounded generic function until concrete types are known.
functioncompare<T>(a: T,b: T): boolean{returna<b;// <- checking deferred as unknown if T is comparable.}compare(1,2);// <- Passes appropriately.compare(1,"a");// <- Fails appropriately: Argument of type 'string' is not assignable to parameter of type 'number'.null<null;// Fails appropriately: Object is possibly 'null'.compare(null,null);// Passes inappropriately, should fail with: Object is possibly 'null'.
#5156 also mentioned non-primitive comparisons which are currently allowed. For objects, I think it only makes sense to allow relational comparison of objects with a [Symbol.toPrimitive] method or a valueOf method returning a primitive. The toString method would be ignored since Object.prototype.toString() returns '[object Object]'.
({}<{});// <- Passes inappropriately, should fail.newDate()<newDate();// <- Passes appropriately since Date.prototype.valueOf() returns a number.
TypeScript generics aren't C++ templates that get instantiated in-place; you can't use that kind of reasoning. The function body isn't visible when the function originates in a .d.ts file, for example.
There is IMHO some inconsistency between different operators, for example TS doesn't throw when comparing incompatible types, but it does so when using +:
declareconstfoo: object|number;foo>5;// I'd expect "Operator '>' cannot be applied to types 'number | object' and 'number'.(2365)" errorfoo+5;// Fails correctly with "Operator '+' cannot be applied to types 'number | object' and 'number'.(2365)"
This is intentional. objOrNum > 5 is considered OK because this only produces true when the LHS is a number, so it's a coherent way to test for a specific number. The same can't be said of + since it produces NaN when the LHS is an object.
Unless by LHS you mean the value after implicit type conversion, this happens in the runtime though, and isn't TYPEScript about TYPES?
#16895 (comment) "explains" why using + operator on incompatible types (string and number) is fine:
This is because + is overloaded as both "arithmetic addition" and "string concatenation"
So is > i'd think. But maybe I'm wrong:
(╯°□°)╯︵ ┻━┻
It also suggested using eslint, but why would we use linter or any other external tools for detecting usage of operators on incompatible TYPES? Isn't what TYPEScript is for?
The same can't be said of + since it produces NaN
Not true
({})+5;// [object Object]5
Moreover, it produces the same result as ({}) + '5', but:
Dividing by 0 produces NaN. Does TS complain about it?
declareconstzero: 0;5/zero;// no error
Could it be because TYPEScript only checks for TYPES and not the runtime result?
But even if object + number produced NaN I don't feel like it should be justification for not allowing it. Yes, it should throw an error, not because of the runtime behaviour though but because TYPEScript should not allow using an operator on incompatible TYPES.
And that's the point - it's not about the result of operation being OK or not, TYPEScript is about TYPES, isn't it? Comparing values having types that are not really comparable is equally wrong as adding or multiplicating them, regardless of the result the operation produces in the runtime.
OK, enough ranting, hopefully noone is offended :) If people like to compare unknown values that may happen to be numbers with numbers then let it be, but wouldn't it be reasonable to introduce some "very strict" setting to detect this kind of problems?
I just found a bug in my code that should have been caught by the type system.
Here's a simplified version of my use case:
/** * A point in time (timestamp). * * Either ISO date string (UTC), Unix timestamp as seconds, Date object or an * object with date properties (UTC). */exporttypeMoment=|string|number|Date|{year: number;month: number;day?: number;hour?: number;minute?: number;second?: number;};someFunction({moment1: "2023-01-01",moment2: 1673513613});functionsomeFunction(params: {moment1: Moment;moment2: Moment}){constmoment1=params.moment1;constmoment2=toTimestamp(params.moment2);// I thought I had converted both moments to numbers (Unix timestamps), so I// can compare the precedence of these timestamps. Compiler should have warned// that number can't be compared to Moment.if(moment1<moment2){// Unexpected things happening}}functiontoTimestamp(input: Moment): number{// ...}
(a: number,b: string)=>a>b;// error, string and number not comparable(a: number,b: string|object)=>a>b;// error, none of the union members are comparable with number(a: number,b: string|object|number)=>a>b;// this is fine, b might be comparable with number
Type safety should work like 'they must be compatible', not like 'they might be compatible'
Activity
DanielRosenwasser commentedon Oct 19, 2020
Was there an undetected regression? The end of #5156 implies this was fixed.
RyanCavanaugh commentedon Oct 19, 2020
Are you actually talking about the generic case or the non-generic case? I'm inclined to agree about the non-generic case but I think erroring on the generic version would be a bridge too far for a lot of users.
lrowe commentedon Oct 20, 2020
For the unbounded generic case in #5156 (comment), I feel the most ergonomic outcome would be to defer the type checking of the unbounded generic function until concrete types are known.
#5156 also mentioned non-primitive comparisons which are currently allowed. For objects, I think it only makes sense to allow relational comparison of objects with a
[Symbol.toPrimitive]method or avalueOfmethod returning a primitive. ThetoStringmethod would be ignored sinceObject.prototype.toString()returns '[object Object]'.RyanCavanaugh commentedon Oct 26, 2020
TypeScript generics aren't C++ templates that get instantiated in-place; you can't use that kind of reasoning. The function body isn't visible when the function originates in a .d.ts file, for example.
jacekkarczmarczyk commentedon Sep 24, 2022
There is IMHO some inconsistency between different operators, for example TS doesn't throw when comparing incompatible types, but it does so when using
+:https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEYD2A7AzgF3gMwFzyQCMArcDAHxQFcBbQkGAbgChnt4A+eAVhfYGoeLVkA
RyanCavanaugh commentedon Sep 26, 2022
This is intentional.
objOrNum > 5is considered OK because this only producestruewhen the LHS is a number, so it's a coherent way to test for a specific number. The same can't be said of+since it producesNaNwhen the LHS is an object.jacekkarczmarczyk commentedon Sep 26, 2022
Not true
Unless by LHS you mean the value after implicit type conversion, this happens in the runtime though, and isn't TYPEScript about TYPES?
#16895 (comment) "explains" why using
+operator on incompatible types (stringandnumber) is fine:So is
>i'd think. But maybe I'm wrong:(╯°□°)╯︵ ┻━┻
It also suggested using eslint, but why would we use linter or any other external tools for detecting usage of operators on incompatible TYPES? Isn't what TYPEScript is for?
Not true
Moreover, it produces the same result as
({}) + '5', but:Dividing by
0producesNaN. Does TS complain about it?Could it be because TYPEScript only checks for TYPES and not the runtime result?
But even if
object + numberproducedNaNI don't feel like it should be justification for not allowing it. Yes, it should throw an error, not because of the runtime behaviour though but because TYPEScript should not allow using an operator on incompatible TYPES.And that's the point - it's not about the result of operation being OK or not, TYPEScript is about TYPES, isn't it? Comparing values having types that are not really comparable is equally wrong as adding or multiplicating them, regardless of the result the operation produces in the runtime.
OK, enough ranting, hopefully noone is offended :) If people like to compare unknown values that may happen to be numbers with numbers then let it be, but wouldn't it be reasonable to introduce some "very strict" setting to detect this kind of problems?
RyanCavanaugh commentedon Sep 27, 2022
I didn't say it was unreasonable, just that the current behavior was motivated from real use cases 🤷♂️
pekam commentedon Jan 12, 2023
+1
I just found a bug in my code that should have been caught by the type system.
Here's a simplified version of my use case:
pekam commentedon Jan 12, 2023
This does not make sense to me:
Type safety should work like 'they must be compatible', not like 'they might be compatible'