From 7996bcbb17e7a18526720cf8b632f55577575b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 29 Dec 2022 23:49:48 +0100 Subject: [PATCH] Improve comparison operators type checking to disallow unions containing numbers as an operand --- src/compiler/checker.ts | 12 +- ...WithNoRelationshipTypeParameter.errors.txt | 98 ++++++++++++++- ...arisonOperatorWithNumberOperand.errors.txt | 11 ++ ...omparisonOperatorWithNumberOperand.symbols | 9 ++ .../comparisonOperatorWithNumberOperand.types | 10 ++ .../controlFlowWhileStatement.errors.txt | 113 ++++++++++++++++++ .../comparisonOperatorWithNumberOperand.ts | 6 + 7 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/comparisonOperatorWithNumberOperand.errors.txt create mode 100644 tests/baselines/reference/comparisonOperatorWithNumberOperand.symbols create mode 100644 tests/baselines/reference/comparisonOperatorWithNumberOperand.types create mode 100644 tests/baselines/reference/controlFlowWhileStatement.errors.txt create mode 100644 tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7ded1be4ea4b8..49db7cf3a930a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -35637,9 +35637,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (checkForDisallowedESSymbolOperand(operator)) { leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left)); rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right)); - reportOperatorErrorUnless((left, right) => - isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || ( - isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType))); + reportOperatorErrorUnless((left, right) => { + if (isTypeAny(left) || isTypeAny(right)) { + return true; + } + const leftAssignableToNumber = isTypeAssignableTo(left, numberOrBigIntType); + const rightAssignableToNumber = isTypeAssignableTo(right, numberOrBigIntType); + return leftAssignableToNumber && rightAssignableToNumber || + !leftAssignableToNumber && !rightAssignableToNumber && areTypesComparable(left, right); + }); } return booleanType; case SyntaxKind.EqualsEqualsToken: diff --git a/tests/baselines/reference/comparisonOperatorWithNoRelationshipTypeParameter.errors.txt b/tests/baselines/reference/comparisonOperatorWithNoRelationshipTypeParameter.errors.txt index e92f8ef20756b..de8a8ac3ce14c 100644 --- a/tests/baselines/reference/comparisonOperatorWithNoRelationshipTypeParameter.errors.txt +++ b/tests/baselines/reference/comparisonOperatorWithNoRelationshipTypeParameter.errors.txt @@ -6,9 +6,41 @@ tests/cases/conformance/expressions/binaryOperators/comparisonOperator/compariso tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(17,14): error TS2367: This comparison appears to be unintentional because the types 'T' and 'U' have no overlap. tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(18,14): error TS2367: This comparison appears to be unintentional because the types 'T' and 'U' have no overlap. tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(19,14): error TS2367: This comparison appears to be unintentional because the types 'T' and 'U' have no overlap. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(23,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(26,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(31,16): error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(34,16): error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(40,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(43,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(48,16): error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(51,16): error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(57,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(60,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(65,16): error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(68,16): error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(74,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(77,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(82,16): error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(85,16): error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(91,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(94,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(99,16): error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(102,16): error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(108,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(111,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(116,16): error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(119,16): error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(125,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(128,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(133,16): error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(136,16): error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(142,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(145,16): error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(150,16): error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts(153,16): error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. -==== tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts (8 errors) ==== +==== tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNoRelationshipTypeParameter.ts (40 errors) ==== enum E { a, b, c } var a: boolean; @@ -48,136 +80,200 @@ tests/cases/conformance/expressions/binaryOperators/comparisonOperator/compariso // operator < var r1a1 = t < a; var r1a2 = t < b; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. var r1a3 = t < c; var r1a4 = t < d; var r1a5 = t < e; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. var r1a6 = t < f; var r1a7 = t < g; var r1b1 = a < t; var r1b2 = b < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. var r1b3 = c < t; var r1b4 = d < t; var r1b5 = e < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. var r1b6 = f < t; var r1b7 = g < t; // operator > var r2a1 = t < a; var r2a2 = t < b; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. var r2a3 = t < c; var r2a4 = t < d; var r2a5 = t < e; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. var r2a6 = t < f; var r2a7 = t < g; var r2b1 = a < t; var r2b2 = b < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. var r2b3 = c < t; var r2b4 = d < t; var r2b5 = e < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. var r2b6 = f < t; var r2b7 = g < t; // operator <= var r3a1 = t < a; var r3a2 = t < b; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. var r3a3 = t < c; var r3a4 = t < d; var r3a5 = t < e; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. var r3a6 = t < f; var r3a7 = t < g; var r3b1 = a < t; var r3b2 = b < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. var r3b3 = c < t; var r3b4 = d < t; var r3b5 = e < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. var r3b6 = f < t; var r3b7 = g < t; // operator >= var r4a1 = t < a; var r4a2 = t < b; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. var r4a3 = t < c; var r4a4 = t < d; var r4a5 = t < e; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. var r4a6 = t < f; var r4a7 = t < g; var r4b1 = a < t; var r4b2 = b < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. var r4b3 = c < t; var r4b4 = d < t; var r4b5 = e < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. var r4b6 = f < t; var r4b7 = g < t; // operator == var r5a1 = t < a; var r5a2 = t < b; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. var r5a3 = t < c; var r5a4 = t < d; var r5a5 = t < e; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. var r5a6 = t < f; var r5a7 = t < g; var r5b1 = a < t; var r5b2 = b < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. var r5b3 = c < t; var r5b4 = d < t; var r5b5 = e < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. var r5b6 = f < t; var r5b7 = g < t; // operator != var r6a1 = t < a; var r6a2 = t < b; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. var r6a3 = t < c; var r6a4 = t < d; var r6a5 = t < e; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. var r6a6 = t < f; var r6a7 = t < g; var r6b1 = a < t; var r6b2 = b < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. var r6b3 = c < t; var r6b4 = d < t; var r6b5 = e < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. var r6b6 = f < t; var r6b7 = g < t; // operator === var r7a1 = t < a; var r7a2 = t < b; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. var r7a3 = t < c; var r7a4 = t < d; var r7a5 = t < e; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. var r7a6 = t < f; var r7a7 = t < g; var r7b1 = a < t; var r7b2 = b < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. var r7b3 = c < t; var r7b4 = d < t; var r7b5 = e < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. var r7b6 = f < t; var r7b7 = g < t; // operator !== var r8a1 = t < a; var r8a2 = t < b; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'number'. var r8a3 = t < c; var r8a4 = t < d; var r8a5 = t < e; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'T' and 'E'. var r8a6 = t < f; var r8a7 = t < g; var r8b1 = a < t; var r8b2 = b < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'number' and 'T'. var r8b3 = c < t; var r8b4 = d < t; var r8b5 = e < t; + ~~~~~ +!!! error TS2365: Operator '<' cannot be applied to types 'E' and 'T'. var r8b6 = f < t; var r8b7 = g < t; } \ No newline at end of file diff --git a/tests/baselines/reference/comparisonOperatorWithNumberOperand.errors.txt b/tests/baselines/reference/comparisonOperatorWithNumberOperand.errors.txt new file mode 100644 index 0000000000000..fe314df34e27f --- /dev/null +++ b/tests/baselines/reference/comparisonOperatorWithNumberOperand.errors.txt @@ -0,0 +1,11 @@ +tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts(3,1): error TS2365: Operator '>=' cannot be applied to types 'number | Promise' and 'number'. + + +==== tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts (1 errors) ==== + // repro #52036 + declare const t1: number | Promise + t1 >= 0 // error + ~~~~~~~ +!!! error TS2365: Operator '>=' cannot be applied to types 'number | Promise' and 'number'. +!!! related TS2773 tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts:3:1: Did you forget to use 'await'? + \ No newline at end of file diff --git a/tests/baselines/reference/comparisonOperatorWithNumberOperand.symbols b/tests/baselines/reference/comparisonOperatorWithNumberOperand.symbols new file mode 100644 index 0000000000000..c13b7484b689d --- /dev/null +++ b/tests/baselines/reference/comparisonOperatorWithNumberOperand.symbols @@ -0,0 +1,9 @@ +=== tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts === +// repro #52036 +declare const t1: number | Promise +>t1 : Symbol(t1, Decl(comparisonOperatorWithNumberOperand.ts, 1, 13)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) + +t1 >= 0 // error +>t1 : Symbol(t1, Decl(comparisonOperatorWithNumberOperand.ts, 1, 13)) + diff --git a/tests/baselines/reference/comparisonOperatorWithNumberOperand.types b/tests/baselines/reference/comparisonOperatorWithNumberOperand.types new file mode 100644 index 0000000000000..3fb38494c09e4 --- /dev/null +++ b/tests/baselines/reference/comparisonOperatorWithNumberOperand.types @@ -0,0 +1,10 @@ +=== tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts === +// repro #52036 +declare const t1: number | Promise +>t1 : number | Promise + +t1 >= 0 // error +>t1 >= 0 : boolean +>t1 : number | Promise +>0 : 0 + diff --git a/tests/baselines/reference/controlFlowWhileStatement.errors.txt b/tests/baselines/reference/controlFlowWhileStatement.errors.txt new file mode 100644 index 0000000000000..9590b052d6a99 --- /dev/null +++ b/tests/baselines/reference/controlFlowWhileStatement.errors.txt @@ -0,0 +1,113 @@ +tests/cases/conformance/controlFlow/controlFlowWhileStatement.ts(81,12): error TS2365: Operator '>' cannot be applied to types 'string | number' and 'number'. + + +==== tests/cases/conformance/controlFlow/controlFlowWhileStatement.ts (1 errors) ==== + let cond: boolean; + function a() { + let x: string | number; + x = ""; + while (cond) { + x; // string + } + } + function b() { + let x: string | number; + x = ""; + while (cond) { + x; // string + x = 42; + break; + } + } + function c() { + let x: string | number; + x = ""; + while (cond) { + x; // string + x = undefined; + if (typeof x === "string") continue; + break; + } + } + function d() { + let x: string | number; + x = ""; + while (x = x.length) { + x; // number + x = ""; + } + } + function e() { + let x: string | number; + x = ""; + while (cond) { + x; // string | number + x = 42; + x; // number + } + x; // string | number + } + function f() { + let x: string | number | boolean | RegExp | Function; + x = ""; + while (cond) { + if (cond) { + x = 42; + break; + } + if (cond) { + x = true; + continue; + } + x = /a/; + } + x; // string | number | boolean | RegExp + } + function g() { + let x: string | number | boolean | RegExp | Function; + x = ""; + while (true) { + if (cond) { + x = 42; + break; + } + if (cond) { + x = true; + continue; + } + x = /a/; + } + x; // number + } + function h1() { + let x: string | number | boolean; + x = ""; + while (x > 1) { + ~~~~~ +!!! error TS2365: Operator '>' cannot be applied to types 'string | number' and 'number'. + x; // string | number + x = 1; + x; // number + } + x; // string | number + } + declare function len(s: string | number): number; + function h2() { + let x: string | number | boolean; + x = ""; + while (cond) { + x = len(x); + x; // number + } + x; // string | number + } + function h3() { + let x: string | number | boolean; + x = ""; + while (cond) { + x; // string | number + x = len(x); + } + x; // string | number + } + \ No newline at end of file diff --git a/tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts b/tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts new file mode 100644 index 0000000000000..e6677484cd768 --- /dev/null +++ b/tests/cases/conformance/expressions/binaryOperators/comparisonOperator/comparisonOperatorWithNumberOperand.ts @@ -0,0 +1,6 @@ +// @strict: true +// @noEmit: true + +// repro #52036 +declare const t1: number | Promise +t1 >= 0 // error