Skip to content

Stricter relational comparisons checking valueOf #52807

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23029,6 +23029,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
type;
}

function getValueOfResult(type: Type): Type {
const valueOf = getPropertyOfType(type, "valueOf" as __String);
if (valueOf) {
const signatures = getSignaturesOfType(getTypeOfSymbol(valueOf), ts.SignatureKind.Call);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI annoying auto-import thing here, but should should just be SignatureKind.Call; sometimes the ts prefix appears first in the completion list and gets used (and that's one reason why I'm trying to get us to remove them everywhere).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main no longer has this ts import so when you merge from main next, you'll have to fix this (and then never have to worry about again).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const signatures = getSignaturesOfType(getTypeOfSymbol(valueOf), ts.SignatureKind.Call);
const signatures = getSignaturesOfType(getTypeOfSymbol(valueOf), SignatureKind.Call);

if (signatures && signatures.length > 0) {
const returnType = getReturnTypeOfSignature(signatures[0]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This favors the first signature in an overload list, while usually in places where we're overload-blind today we usually favor the last overload in the list. Maybe signatures[signatures.length - 1] for consistency? Or, potentially better yet, rather than picking one overload, getUnionType(map(signatures, getReturnTypeOfSignature)) just to hedge (in theory, only overloads that match an all-undefined/empty argument list would actually apply.... but overloads on valueOf are probably a bit suspicious anyway). I say that, but we'll actually see an overloaded valueOf for every intersected type eg, number & {myBrand} is going to have a valueOf from number's apparent type and object's apparent type intersected together, which'll result in two overloads unless they're merged for being identical.

return returnType;
}
}
return type;
}

function getWidenedLiteralType(type: Type): Type {
return type.flags & TypeFlags.EnumLike && isFreshLiteralType(type) ? getBaseTypeOfEnumLikeType(type as LiteralType) :
type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType :
Expand Down Expand Up @@ -36498,10 +36510,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (isTypeAny(left) || isTypeAny(right)) {
return true;
}
// Only collect 'valueOf' results here so that the error message doesn't display
// "cannot compare Object to Object" when comparing e.g. [] > []
if (!(left.flags & TypeFlags.Primitive)) left = getValueOfResult(left);
if (!(right.flags & TypeFlags.Primitive)) right = getValueOfResult(right);

const leftAssignableToNumber = isTypeAssignableTo(left, numberOrBigIntType);
const rightAssignableToNumber = isTypeAssignableTo(right, numberOrBigIntType);
return leftAssignableToNumber && rightAssignableToNumber ||
!leftAssignableToNumber && !rightAssignableToNumber && areTypesComparable(left, right);
if (leftAssignableToNumber) {
const rightAssignableToNumber = isTypeAssignableTo(right, numberOrBigIntType);
if (rightAssignableToNumber) return true;
}
return isTypeAssignableTo(left, stringType) && isTypeAssignableTo(right, stringType);
});
}
return booleanType;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2169,7 +2169,7 @@ export function equateStringsCaseSensitive(a: string, b: string) {

function compareComparableValues(a: string | undefined, b: string | undefined): Comparison;
function compareComparableValues(a: number | undefined, b: number | undefined): Comparison;
function compareComparableValues(a: string | number | undefined, b: string | number | undefined) {
function compareComparableValues(a: any, b: any) {
return a === b ? Comparison.EqualTo :
a === undefined ? Comparison.LessThan :
b === undefined ? Comparison.GreaterThan :
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(11,11): error TS2365: Operator '<' cannot be applied to types 'boolean' and 'boolean'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(13,11): error TS2365: Operator '<' cannot be applied to types 'void' and 'void'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(15,11): error TS18050: The value 'null' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(15,18): error TS18050: The value 'null' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(16,11): error TS18050: The value 'undefined' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(16,23): error TS18050: The value 'undefined' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(20,11): error TS2365: Operator '>' cannot be applied to types 'boolean' and 'boolean'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(22,11): error TS2365: Operator '>' cannot be applied to types 'void' and 'void'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(24,11): error TS18050: The value 'null' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(24,18): error TS18050: The value 'null' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(25,11): error TS18050: The value 'undefined' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(25,23): error TS18050: The value 'undefined' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(29,11): error TS2365: Operator '<=' cannot be applied to types 'boolean' and 'boolean'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(31,11): error TS2365: Operator '<=' cannot be applied to types 'void' and 'void'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(33,11): error TS18050: The value 'null' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(33,19): error TS18050: The value 'null' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(34,11): error TS18050: The value 'undefined' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(34,24): error TS18050: The value 'undefined' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(38,11): error TS2365: Operator '>=' cannot be applied to types 'boolean' and 'boolean'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(40,11): error TS2365: Operator '>=' cannot be applied to types 'void' and 'void'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(42,11): error TS18050: The value 'null' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(42,19): error TS18050: The value 'null' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(43,11): error TS18050: The value 'undefined' cannot be used here.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts(43,24): error TS18050: The value 'undefined' cannot be used here.


==== tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts (16 errors) ====
==== tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalPrimitiveType.ts (24 errors) ====
enum E { a, b, c }

var a: number;
Expand All @@ -28,8 +36,12 @@ tests/cases/conformance/expressions/binaryOperators/comparisonOperator/compariso
// operator <
var ra1 = a < a;
var ra2 = b < b;
~~~~~
!!! error TS2365: Operator '<' cannot be applied to types 'boolean' and 'boolean'.
var ra3 = c < c;
var ra4 = d < d;
~~~~~
!!! error TS2365: Operator '<' cannot be applied to types 'void' and 'void'.
var ra5 = e < e;
var ra6 = null < null;
~~~~
Expand All @@ -45,8 +57,12 @@ tests/cases/conformance/expressions/binaryOperators/comparisonOperator/compariso
// operator >
var rb1 = a > a;
var rb2 = b > b;
~~~~~
!!! error TS2365: Operator '>' cannot be applied to types 'boolean' and 'boolean'.
var rb3 = c > c;
var rb4 = d > d;
~~~~~
!!! error TS2365: Operator '>' cannot be applied to types 'void' and 'void'.
var rb5 = e > e;
var rb6 = null > null;
~~~~
Expand All @@ -62,8 +78,12 @@ tests/cases/conformance/expressions/binaryOperators/comparisonOperator/compariso
// operator <=
var rc1 = a <= a;
var rc2 = b <= b;
~~~~~~
!!! error TS2365: Operator '<=' cannot be applied to types 'boolean' and 'boolean'.
var rc3 = c <= c;
var rc4 = d <= d;
~~~~~~
!!! error TS2365: Operator '<=' cannot be applied to types 'void' and 'void'.
var rc5 = e <= e;
var rc6 = null <= null;
~~~~
Expand All @@ -79,8 +99,12 @@ tests/cases/conformance/expressions/binaryOperators/comparisonOperator/compariso
// operator >=
var rd1 = a >= a;
var rd2 = b >= b;
~~~~~~
!!! error TS2365: Operator '>=' cannot be applied to types 'boolean' and 'boolean'.
var rd3 = c >= c;
var rd4 = d >= d;
~~~~~~
!!! error TS2365: Operator '>=' cannot be applied to types 'void' and 'void'.
var rd5 = e >= e;
var rd6 = null >= null;
~~~~
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalTypeParameter.ts(2,14): error TS2365: Operator '<' cannot be applied to types 'T' and 'T'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalTypeParameter.ts(3,14): error TS2365: Operator '>' cannot be applied to types 'T' and 'T'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalTypeParameter.ts(4,14): error TS2365: Operator '<=' cannot be applied to types 'T' and 'T'.
tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalTypeParameter.ts(5,14): error TS2365: Operator '>=' cannot be applied to types 'T' and 'T'.


==== tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithIdenticalTypeParameter.ts (4 errors) ====
function foo<T>(t: T) {
var r1 = t < t;
~~~~~
!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'T'.
var r2 = t > t;
~~~~~
!!! error TS2365: Operator '>' cannot be applied to types 'T' and 'T'.
var r3 = t <= t;
~~~~~~
!!! error TS2365: Operator '<=' cannot be applied to types 'T' and 'T'.
var r4 = t >= t;
~~~~~~
!!! error TS2365: Operator '>=' cannot be applied to types 'T' and 'T'.
var r5 = t == t;
var r6 = t != t;
var r7 = t === t;
var r8 = t !== t;
}
Loading