From 945af923239622f1bb589d5be817d77388becea0 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 21 Sep 2021 11:00:29 +0800 Subject: [PATCH 1/5] feat: support error when comparing with object/array literals --- src/compiler/checker.ts | 4 + src/compiler/diagnosticMessages.json | 4 + ...itionalEqualityOnLiteralObjects.errors.txt | 130 +++++++++++++ .../conditionalEqualityOnLiteralObjects.js | 108 ++++++++++ ...onditionalEqualityOnLiteralObjects.symbols | 92 +++++++++ .../conditionalEqualityOnLiteralObjects.types | 184 ++++++++++++++++++ .../reference/narrowByEquality.errors.txt | 5 +- .../reference/parserRealSource7.errors.txt | 5 +- .../conditionalEqualityOnLiteralObjects.ts | 54 +++++ 9 files changed, 584 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/conditionalEqualityOnLiteralObjects.errors.txt create mode 100644 tests/baselines/reference/conditionalEqualityOnLiteralObjects.js create mode 100644 tests/baselines/reference/conditionalEqualityOnLiteralObjects.symbols create mode 100644 tests/baselines/reference/conditionalEqualityOnLiteralObjects.types create mode 100644 tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 025eeecc30624..daf36ddd3abc2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33975,6 +33975,10 @@ namespace ts { case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: + if (left.kind === SyntaxKind.ArrayLiteralExpression || left.kind === SyntaxKind.ObjectLiteralExpression || right.kind === SyntaxKind.ArrayLiteralExpression || right.kind === SyntaxKind.ObjectLiteralExpression) { + const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; + error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true"); + } reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); return booleanType; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index fa6b9fd3efc55..5a8470eacf170 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3455,6 +3455,10 @@ "category": "Error", "code": 2838 }, + "This condition will always return '{0}' since JavaScript compares objects by reference, not value.": { + "category": "Error", + "code": 2839 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/conditionalEqualityOnLiteralObjects.errors.txt b/tests/baselines/reference/conditionalEqualityOnLiteralObjects.errors.txt new file mode 100644 index 0000000000000..f76436aa33877 --- /dev/null +++ b/tests/baselines/reference/conditionalEqualityOnLiteralObjects.errors.txt @@ -0,0 +1,130 @@ +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(4,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(6,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(8,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(10,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(12,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(14,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(17,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(19,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(21,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(23,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(25,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(27,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(30,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(32,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(34,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(36,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(38,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(40,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(43,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(45,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(47,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(49,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(51,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. +tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts(53,5): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + + +==== tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts (24 errors) ==== + const a = { a: 1 } + const b = [1] + + if ({ a: 1 } === { a: 1 }) { + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if ([1] === [1]) { + ~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if (a === { a: 1 }) { + ~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if (b === [1]) { + ~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if ({ a: 1 } === a) { + ~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if ([1] === b) { + ~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + + if ({ a: 1 } !== { a: 1 }) { + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if ([1] !== [1]) { + ~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if (a !== { a: 1 }) { + ~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if (b !== [1]) { + ~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if ({ a: 1 } !== a) { + ~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if ([1] !== b) { + ~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + + if ({ a: 1 } == { a: 1 }) { + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if ([1] == [1]) { + ~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if (a == { a: 1 }) { + ~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if (b == [1]) { + ~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if ({ a: 1 } == a) { + ~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + if ([1] == b) { + ~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. + } + + if ({ a: 1 } != { a: 1 }) { + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if ([1] != [1]) { + ~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if (a != { a: 1 }) { + ~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if (b != [1]) { + ~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if ({ a: 1 } != a) { + ~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + if ([1] != b) { + ~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. + } + \ No newline at end of file diff --git a/tests/baselines/reference/conditionalEqualityOnLiteralObjects.js b/tests/baselines/reference/conditionalEqualityOnLiteralObjects.js new file mode 100644 index 0000000000000..ab1ca51bb722e --- /dev/null +++ b/tests/baselines/reference/conditionalEqualityOnLiteralObjects.js @@ -0,0 +1,108 @@ +//// [conditionalEqualityOnLiteralObjects.ts] +const a = { a: 1 } +const b = [1] + +if ({ a: 1 } === { a: 1 }) { +} +if ([1] === [1]) { +} +if (a === { a: 1 }) { +} +if (b === [1]) { +} +if ({ a: 1 } === a) { +} +if ([1] === b) { +} + +if ({ a: 1 } !== { a: 1 }) { +} +if ([1] !== [1]) { +} +if (a !== { a: 1 }) { +} +if (b !== [1]) { +} +if ({ a: 1 } !== a) { +} +if ([1] !== b) { +} + +if ({ a: 1 } == { a: 1 }) { +} +if ([1] == [1]) { +} +if (a == { a: 1 }) { +} +if (b == [1]) { +} +if ({ a: 1 } == a) { +} +if ([1] == b) { +} + +if ({ a: 1 } != { a: 1 }) { +} +if ([1] != [1]) { +} +if (a != { a: 1 }) { +} +if (b != [1]) { +} +if ({ a: 1 } != a) { +} +if ([1] != b) { +} + + +//// [conditionalEqualityOnLiteralObjects.js] +var a = { a: 1 }; +var b = [1]; +if ({ a: 1 } === { a: 1 }) { +} +if ([1] === [1]) { +} +if (a === { a: 1 }) { +} +if (b === [1]) { +} +if ({ a: 1 } === a) { +} +if ([1] === b) { +} +if ({ a: 1 } !== { a: 1 }) { +} +if ([1] !== [1]) { +} +if (a !== { a: 1 }) { +} +if (b !== [1]) { +} +if ({ a: 1 } !== a) { +} +if ([1] !== b) { +} +if ({ a: 1 } == { a: 1 }) { +} +if ([1] == [1]) { +} +if (a == { a: 1 }) { +} +if (b == [1]) { +} +if ({ a: 1 } == a) { +} +if ([1] == b) { +} +if ({ a: 1 } != { a: 1 }) { +} +if ([1] != [1]) { +} +if (a != { a: 1 }) { +} +if (b != [1]) { +} +if ({ a: 1 } != a) { +} +if ([1] != b) { +} diff --git a/tests/baselines/reference/conditionalEqualityOnLiteralObjects.symbols b/tests/baselines/reference/conditionalEqualityOnLiteralObjects.symbols new file mode 100644 index 0000000000000..0be4f1e5c9748 --- /dev/null +++ b/tests/baselines/reference/conditionalEqualityOnLiteralObjects.symbols @@ -0,0 +1,92 @@ +=== tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts === +const a = { a: 1 } +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 11)) + +const b = [1] +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) + +if ({ a: 1 } === { a: 1 }) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 3, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 3, 18)) +} +if ([1] === [1]) { +} +if (a === { a: 1 }) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 7, 11)) +} +if (b === [1]) { +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) +} +if ({ a: 1 } === a) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 11, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +} +if ([1] === b) { +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) +} + +if ({ a: 1 } !== { a: 1 }) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 16, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 16, 18)) +} +if ([1] !== [1]) { +} +if (a !== { a: 1 }) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 20, 11)) +} +if (b !== [1]) { +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) +} +if ({ a: 1 } !== a) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 24, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +} +if ([1] !== b) { +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) +} + +if ({ a: 1 } == { a: 1 }) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 29, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 29, 17)) +} +if ([1] == [1]) { +} +if (a == { a: 1 }) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 33, 10)) +} +if (b == [1]) { +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) +} +if ({ a: 1 } == a) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 37, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +} +if ([1] == b) { +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) +} + +if ({ a: 1 } != { a: 1 }) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 42, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 42, 17)) +} +if ([1] != [1]) { +} +if (a != { a: 1 }) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 46, 10)) +} +if (b != [1]) { +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) +} +if ({ a: 1 } != a) { +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 50, 5)) +>a : Symbol(a, Decl(conditionalEqualityOnLiteralObjects.ts, 0, 5)) +} +if ([1] != b) { +>b : Symbol(b, Decl(conditionalEqualityOnLiteralObjects.ts, 1, 5)) +} + diff --git a/tests/baselines/reference/conditionalEqualityOnLiteralObjects.types b/tests/baselines/reference/conditionalEqualityOnLiteralObjects.types new file mode 100644 index 0000000000000..8287257837c16 --- /dev/null +++ b/tests/baselines/reference/conditionalEqualityOnLiteralObjects.types @@ -0,0 +1,184 @@ +=== tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts === +const a = { a: 1 } +>a : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +const b = [1] +>b : number[] +>[1] : number[] +>1 : 1 + +if ({ a: 1 } === { a: 1 }) { +>{ a: 1 } === { a: 1 } : boolean +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +} +if ([1] === [1]) { +>[1] === [1] : boolean +>[1] : number[] +>1 : 1 +>[1] : number[] +>1 : 1 +} +if (a === { a: 1 }) { +>a === { a: 1 } : boolean +>a : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +} +if (b === [1]) { +>b === [1] : boolean +>b : number[] +>[1] : number[] +>1 : 1 +} +if ({ a: 1 } === a) { +>{ a: 1 } === a : boolean +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>a : { a: number; } +} +if ([1] === b) { +>[1] === b : boolean +>[1] : number[] +>1 : 1 +>b : number[] +} + +if ({ a: 1 } !== { a: 1 }) { +>{ a: 1 } !== { a: 1 } : boolean +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +} +if ([1] !== [1]) { +>[1] !== [1] : boolean +>[1] : number[] +>1 : 1 +>[1] : number[] +>1 : 1 +} +if (a !== { a: 1 }) { +>a !== { a: 1 } : boolean +>a : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +} +if (b !== [1]) { +>b !== [1] : boolean +>b : number[] +>[1] : number[] +>1 : 1 +} +if ({ a: 1 } !== a) { +>{ a: 1 } !== a : boolean +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>a : { a: number; } +} +if ([1] !== b) { +>[1] !== b : boolean +>[1] : number[] +>1 : 1 +>b : number[] +} + +if ({ a: 1 } == { a: 1 }) { +>{ a: 1 } == { a: 1 } : boolean +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +} +if ([1] == [1]) { +>[1] == [1] : boolean +>[1] : number[] +>1 : 1 +>[1] : number[] +>1 : 1 +} +if (a == { a: 1 }) { +>a == { a: 1 } : boolean +>a : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +} +if (b == [1]) { +>b == [1] : boolean +>b : number[] +>[1] : number[] +>1 : 1 +} +if ({ a: 1 } == a) { +>{ a: 1 } == a : boolean +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>a : { a: number; } +} +if ([1] == b) { +>[1] == b : boolean +>[1] : number[] +>1 : 1 +>b : number[] +} + +if ({ a: 1 } != { a: 1 }) { +>{ a: 1 } != { a: 1 } : boolean +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +} +if ([1] != [1]) { +>[1] != [1] : boolean +>[1] : number[] +>1 : 1 +>[1] : number[] +>1 : 1 +} +if (a != { a: 1 }) { +>a != { a: 1 } : boolean +>a : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +} +if (b != [1]) { +>b != [1] : boolean +>b : number[] +>[1] : number[] +>1 : 1 +} +if ({ a: 1 } != a) { +>{ a: 1 } != a : boolean +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 +>a : { a: number; } +} +if ([1] != b) { +>[1] != b : boolean +>[1] : number[] +>1 : 1 +>b : number[] +} + diff --git a/tests/baselines/reference/narrowByEquality.errors.txt b/tests/baselines/reference/narrowByEquality.errors.txt index 15610d6d1d49a..a484b58951576 100644 --- a/tests/baselines/reference/narrowByEquality.errors.txt +++ b/tests/baselines/reference/narrowByEquality.errors.txt @@ -1,10 +1,11 @@ +tests/cases/compiler/narrowByEquality.ts(41,5): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. tests/cases/compiler/narrowByEquality.ts(54,15): error TS2322: Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'. tests/cases/compiler/narrowByEquality.ts(55,9): error TS2322: Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'. -==== tests/cases/compiler/narrowByEquality.ts (2 errors) ==== +==== tests/cases/compiler/narrowByEquality.ts (3 errors) ==== declare let x: number | string | boolean declare let n: number; declare let s: string; @@ -46,6 +47,8 @@ tests/cases/compiler/narrowByEquality.ts(55,9): error TS2322: Type 'string | num declare let xAndObj: number | string | boolean | object if (xAndObj == {}) { + ~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. xAndObj; } diff --git a/tests/baselines/reference/parserRealSource7.errors.txt b/tests/baselines/reference/parserRealSource7.errors.txt index 250b589873b37..a566f7212e3ff 100644 --- a/tests/baselines/reference/parserRealSource7.errors.txt +++ b/tests/baselines/reference/parserRealSource7.errors.txt @@ -265,6 +265,7 @@ tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(704,34): error T tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(710,69): error TS2304: Cannot find name 'NodeType'. tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(710,93): error TS2304: Cannot find name 'FuncDecl'. tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(710,136): error TS2304: Cannot find name 'FuncDecl'. +tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(722,17): error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(724,22): error TS2304: Cannot find name 'hasFlag'. tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(724,83): error TS2304: Cannot find name 'FncFlags'. tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(729,44): error TS2304: Cannot find name 'SymbolKind'. @@ -303,7 +304,7 @@ tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(827,34): error T tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(828,13): error TS2304: Cannot find name 'popTypeCollectionScope'. -==== tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts (303 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts (304 errors) ==== // Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0. // See LICENSE.txt in the project root for complete license information. @@ -1562,6 +1563,8 @@ tests/cases/conformance/parser/ecmascript5/parserRealSource7.ts(828,13): error T fgSym.type && fgSym.type.construct && fgSym.type.construct.signatures != [] && + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2839: This condition will always return 'true' since JavaScript compares objects by reference, not value. (fgSym.type.construct.signatures[0].declAST == null || !hasFlag(fgSym.type.construct.signatures[0].declAST.fncFlags, FncFlags.Ambient)) && ~~~~~~~ diff --git a/tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts b/tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts new file mode 100644 index 0000000000000..ef4a6f949c493 --- /dev/null +++ b/tests/cases/compiler/conditionalEqualityOnLiteralObjects.ts @@ -0,0 +1,54 @@ +const a = { a: 1 } +const b = [1] + +if ({ a: 1 } === { a: 1 }) { +} +if ([1] === [1]) { +} +if (a === { a: 1 }) { +} +if (b === [1]) { +} +if ({ a: 1 } === a) { +} +if ([1] === b) { +} + +if ({ a: 1 } !== { a: 1 }) { +} +if ([1] !== [1]) { +} +if (a !== { a: 1 }) { +} +if (b !== [1]) { +} +if ({ a: 1 } !== a) { +} +if ([1] !== b) { +} + +if ({ a: 1 } == { a: 1 }) { +} +if ([1] == [1]) { +} +if (a == { a: 1 }) { +} +if (b == [1]) { +} +if ({ a: 1 } == a) { +} +if ([1] == b) { +} + +if ({ a: 1 } != { a: 1 }) { +} +if ([1] != [1]) { +} +if (a != { a: 1 }) { +} +if (b != [1]) { +} +if ({ a: 1 } != a) { +} +if ([1] != b) { +} From 0cca35320b1875dc92f1d682a6b7fdbcbdcdb51b Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 21 Sep 2021 11:06:51 +0800 Subject: [PATCH 2/5] chore: include regexp, function and class literal --- src/compiler/checker.ts | 3 ++- src/compiler/utilitiesPublic.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index daf36ddd3abc2..ce26fc0b70662 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33975,7 +33975,8 @@ namespace ts { case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: - if (left.kind === SyntaxKind.ArrayLiteralExpression || left.kind === SyntaxKind.ObjectLiteralExpression || right.kind === SyntaxKind.ArrayLiteralExpression || right.kind === SyntaxKind.ObjectLiteralExpression) { + isLiteralExpression + if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) { const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true"); } diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 2f4ed3b14ccc1..4eca9fefba21b 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1114,6 +1114,19 @@ namespace ts { return isLiteralKind(node.kind); } + /** @internal */ + export function isLiteralExpressionOfObject(node: Node) { + switch (node.kind) { + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassExpression: + return true; + } + return false; + } + // Pseudo-literals /* @internal */ From 58193b4ce2d49f1829cf876ecfbbe39b9a3c04c3 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 21 Sep 2021 11:07:25 +0800 Subject: [PATCH 3/5] chore: include regexp, function and class literal --- src/compiler/checker.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ce26fc0b70662..4915d0cff6679 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33975,7 +33975,6 @@ namespace ts { case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: - isLiteralExpression if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) { const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true"); From 00b208419863943d9b9b473fbb2dd8a37a768c55 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Tue, 21 Sep 2021 11:25:15 +0800 Subject: [PATCH 4/5] test: update baseline --- .../functionImplementationErrors.errors.txt | 8 +- .../functionImplementations.errors.txt | 164 ++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/functionImplementations.errors.txt diff --git a/tests/baselines/reference/functionImplementationErrors.errors.txt b/tests/baselines/reference/functionImplementationErrors.errors.txt index 5a9ebea09be94..27452a04175d3 100644 --- a/tests/baselines/reference/functionImplementationErrors.errors.txt +++ b/tests/baselines/reference/functionImplementationErrors.errors.txt @@ -1,9 +1,10 @@ tests/cases/conformance/functions/functionImplementationErrors.ts(25,16): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. tests/cases/conformance/functions/functionImplementationErrors.ts(30,17): error TS2373: Parameter 'n' cannot reference identifier 'm' declared after it. tests/cases/conformance/functions/functionImplementationErrors.ts(35,17): error TS2373: Parameter 'n' cannot reference identifier 'm' declared after it. +tests/cases/conformance/functions/functionImplementationErrors.ts(40,1): error TS2838: This condition will always return 'false' since JavaScript compares objects by reference, not value. -==== tests/cases/conformance/functions/functionImplementationErrors.ts (3 errors) ==== +==== tests/cases/conformance/functions/functionImplementationErrors.ts (4 errors) ==== // FunctionExpression with no return type annotation with multiple return statements with unrelated types var f1 = function () { return ''; @@ -50,9 +51,14 @@ tests/cases/conformance/functions/functionImplementationErrors.ts(35,17): error // FunctionExpression with non -void return type annotation with a throw, no return, and other code // Should be error but isn't undefined === function (): number { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ throw undefined; + ~~~~~~~~~~~~~~~~~~~~ var x = 4; + ~~~~~~~~~~~~~~ }; + ~ +!!! error TS2838: This condition will always return 'false' since JavaScript compares objects by reference, not value. class Base { private x; } class AnotherClass { private y; } diff --git a/tests/baselines/reference/functionImplementations.errors.txt b/tests/baselines/reference/functionImplementations.errors.txt new file mode 100644 index 0000000000000..a67ddc9d00e24 --- /dev/null +++ b/tests/baselines/reference/functionImplementations.errors.txt @@ -0,0 +1,164 @@ +tests/cases/conformance/functions/functionImplementations.ts(90,1): error TS2838: This condition will always return 'false' since JavaScript compares objects by reference, not value. + + +==== tests/cases/conformance/functions/functionImplementations.ts (1 errors) ==== + // FunctionExpression with no return type annotation and no return statement returns void + var v: void = function () { } (); + + // FunctionExpression f with no return type annotation and directly references f in its body returns any + var a: any = function f() { + return f; + }; + var a: any = function f() { + return f(); + }; + + // FunctionExpression f with no return type annotation and indirectly references f in its body returns any + var a: any = function f() { + var x = f; + return x; + }; + + // Two mutually recursive function implementations with no return type annotations + function rec1() { + return rec2(); + } + function rec2() { + return rec1(); + } + var a = rec1(); + var a = rec2(); + + // Two mutually recursive function implementations with return type annotation in one + function rec3(): number { + return rec4(); + } + function rec4() { + return rec3(); + } + var n: number; + var n = rec3(); + var n = rec4(); + + // FunctionExpression with no return type annotation and returns a number + var n = function () { + return 3; + } (); + + // FunctionExpression with no return type annotation and returns null + var nu = null; + var nu = function () { + return null; + } (); + + // FunctionExpression with no return type annotation and returns undefined + var un = undefined; + var un = function () { + return undefined; + } (); + + // FunctionExpression with no return type annotation and returns a type parameter type + var n = function (x: T) { + return x; + } (4); + + // FunctionExpression with no return type annotation and returns a constrained type parameter type + var n = function (x: T) { + return x; + } (4); + + // FunctionExpression with no return type annotation with multiple return statements with identical types + var n = function () { + return 3; + return 5; + }(); + + // Otherwise, the inferred return type is the first of the types of the return statement expressions + // in the function body that is a supertype of each of the others, + // ignoring return statements with no expressions. + // A compile - time error occurs if no return statement expression has a type that is a supertype of each of the others. + // FunctionExpression with no return type annotation with multiple return statements with subtype relation between returns + class Base { private m; } + class Derived extends Base { private q; } + var b: Base; + var b = function () { + return new Base(); return new Derived(); + } (); + + // FunctionExpression with no return type annotation with multiple return statements with one a recursive call + var a = function f() { + return new Base(); return new Derived(); return f(); // ? + } (); + + // FunctionExpression with non -void return type annotation with a single throw statement + undefined === function (): number { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + throw undefined; + ~~~~~~~~~~~~~~~~~~~~ + }; + ~ +!!! error TS2838: This condition will always return 'false' since JavaScript compares objects by reference, not value. + + // Type of 'this' in function implementation is 'any' + function thisFunc() { + var x = this; + var x: any; + } + + // Function signature with optional parameter, no type annotation and initializer has initializer's type + function opt1(n = 4) { + var m = n; + var m: number; + } + + // Function signature with optional parameter, no type annotation and initializer has initializer's widened type + function opt2(n = { x: null, y: undefined }) { + var m = n; + var m: { x: any; y: any }; + } + + // Function signature with initializer referencing other parameter to the left + function opt3(n: number, m = n) { + var y = m; + var y: number; + } + + // Function signature with optional parameter has correct codegen + // (tested above) + + // FunctionExpression with non -void return type annotation return with no expression + function f6(): number { + return; + } + + class Derived2 extends Base { private r: string; } + class AnotherClass { private x } + // if f is a contextually typed function expression, the inferred return type is the union type + // of the types of the return statement expressions in the function body, + // ignoring return statements with no expressions. + var f7: (x: number) => string | number = x => { // should be (x: number) => number | string + if (x < 0) { return x; } + return x.toString(); + } + var f8: (x: number) => any = x => { // should be (x: number) => Base + return new Base(); + return new Derived2(); + } + var f9: (x: number) => any = x => { // should be (x: number) => Base + return new Base(); + return new Derived(); + return new Derived2(); + } + var f10: (x: number) => any = x => { // should be (x: number) => Derived | Derived1 + return new Derived(); + return new Derived2(); + } + var f11: (x: number) => any = x => { // should be (x: number) => Base | AnotherClass + return new Base(); + return new AnotherClass(); + } + var f12: (x: number) => any = x => { // should be (x: number) => Base | AnotherClass + return new Base(); + return; // should be ignored + return new AnotherClass(); + } \ No newline at end of file From 9659667e545d662b75bfac418eb905d1f0d41a3c Mon Sep 17 00:00:00 2001 From: Jack Works Date: Thu, 12 May 2022 00:07:37 +0800 Subject: [PATCH 5/5] fix: baseline --- .../reference/functionImplementationErrors.errors.txt | 4 ++-- tests/baselines/reference/functionImplementations.errors.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/functionImplementationErrors.errors.txt b/tests/baselines/reference/functionImplementationErrors.errors.txt index 27452a04175d3..cdd16bc902718 100644 --- a/tests/baselines/reference/functionImplementationErrors.errors.txt +++ b/tests/baselines/reference/functionImplementationErrors.errors.txt @@ -1,7 +1,7 @@ tests/cases/conformance/functions/functionImplementationErrors.ts(25,16): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. tests/cases/conformance/functions/functionImplementationErrors.ts(30,17): error TS2373: Parameter 'n' cannot reference identifier 'm' declared after it. tests/cases/conformance/functions/functionImplementationErrors.ts(35,17): error TS2373: Parameter 'n' cannot reference identifier 'm' declared after it. -tests/cases/conformance/functions/functionImplementationErrors.ts(40,1): error TS2838: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/conformance/functions/functionImplementationErrors.ts(40,1): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. ==== tests/cases/conformance/functions/functionImplementationErrors.ts (4 errors) ==== @@ -58,7 +58,7 @@ tests/cases/conformance/functions/functionImplementationErrors.ts(40,1): error T ~~~~~~~~~~~~~~ }; ~ -!!! error TS2838: This condition will always return 'false' since JavaScript compares objects by reference, not value. +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. class Base { private x; } class AnotherClass { private y; } diff --git a/tests/baselines/reference/functionImplementations.errors.txt b/tests/baselines/reference/functionImplementations.errors.txt index a67ddc9d00e24..e289bd7e40039 100644 --- a/tests/baselines/reference/functionImplementations.errors.txt +++ b/tests/baselines/reference/functionImplementations.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/functions/functionImplementations.ts(90,1): error TS2838: This condition will always return 'false' since JavaScript compares objects by reference, not value. +tests/cases/conformance/functions/functionImplementations.ts(90,1): error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. ==== tests/cases/conformance/functions/functionImplementations.ts (1 errors) ==== @@ -97,7 +97,7 @@ tests/cases/conformance/functions/functionImplementations.ts(90,1): error TS2838 ~~~~~~~~~~~~~~~~~~~~ }; ~ -!!! error TS2838: This condition will always return 'false' since JavaScript compares objects by reference, not value. +!!! error TS2839: This condition will always return 'false' since JavaScript compares objects by reference, not value. // Type of 'this' in function implementation is 'any' function thisFunc() {