From ba393b6b9f6ee5dee67e4d6727e4fba10d57b4df Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Fri, 30 Dec 2022 02:36:59 +0200 Subject: [PATCH] fix(51760): Nullish-coalescing assignment narrows type to assigned value (#51767) * fix(51760): skip type guard errors for narrowed nullish-coalescing assignment * update tests --- src/compiler/checker.ts | 2 +- ...llishCoalescingAssignment(strict=false).js | 34 ++++++++++++ ...CoalescingAssignment(strict=false).symbols | 41 ++++++++++++++ ...shCoalescingAssignment(strict=false).types | 55 +++++++++++++++++++ ...ullishCoalescingAssignment(strict=true).js | 35 ++++++++++++ ...hCoalescingAssignment(strict=true).symbols | 41 ++++++++++++++ ...ishCoalescingAssignment(strict=true).types | 55 +++++++++++++++++++ ...qualityWithtNullishCoalescingAssignment.ts | 18 ++++++ 8 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).js create mode 100644 tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).symbols create mode 100644 tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).types create mode 100644 tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).js create mode 100644 tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).symbols create mode 100644 tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).types create mode 100644 tests/cases/conformance/types/typeRelationships/comparable/equalityWithtNullishCoalescingAssignment.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7ded1be4ea4..e9d4e8aa44f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26819,7 +26819,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` if (isExpressionOfOptionalChainRoot(expr) || - isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { + isBinaryExpression(expr.parent) && (expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken || expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionEqualsToken) && expr.parent.left === expr) { return narrowTypeByOptionality(type, expr, assumeTrue); } switch (expr.kind) { diff --git a/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).js b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).js new file mode 100644 index 00000000000..ab2bf800348 --- /dev/null +++ b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).js @@ -0,0 +1,34 @@ +//// [equalityWithtNullishCoalescingAssignment.ts] +function f1(a?: boolean): void { + a ??= true; + + if (a === false) { + console.log(a); + } +} +f1(false); + +function f2() { + let x: 0 | 1 | 2 | 3 = 0 as any; + x ??= 1; + if (x === 0) { + console.log(x); + } +} + + +//// [equalityWithtNullishCoalescingAssignment.js] +function f1(a) { + a !== null && a !== void 0 ? a : (a = true); + if (a === false) { + console.log(a); + } +} +f1(false); +function f2() { + var x = 0; + x !== null && x !== void 0 ? x : (x = 1); + if (x === 0) { + console.log(x); + } +} diff --git a/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).symbols b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).symbols new file mode 100644 index 00000000000..7377bc3dd55 --- /dev/null +++ b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).symbols @@ -0,0 +1,41 @@ +=== tests/cases/conformance/types/typeRelationships/comparable/equalityWithtNullishCoalescingAssignment.ts === +function f1(a?: boolean): void { +>f1 : Symbol(f1, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 0)) +>a : Symbol(a, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 12)) + + a ??= true; +>a : Symbol(a, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 12)) + + if (a === false) { +>a : Symbol(a, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 12)) + + console.log(a); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>a : Symbol(a, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 12)) + } +} +f1(false); +>f1 : Symbol(f1, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 0)) + +function f2() { +>f2 : Symbol(f2, Decl(equalityWithtNullishCoalescingAssignment.ts, 7, 10)) + + let x: 0 | 1 | 2 | 3 = 0 as any; +>x : Symbol(x, Decl(equalityWithtNullishCoalescingAssignment.ts, 10, 7)) + + x ??= 1; +>x : Symbol(x, Decl(equalityWithtNullishCoalescingAssignment.ts, 10, 7)) + + if (x === 0) { +>x : Symbol(x, Decl(equalityWithtNullishCoalescingAssignment.ts, 10, 7)) + + console.log(x); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>x : Symbol(x, Decl(equalityWithtNullishCoalescingAssignment.ts, 10, 7)) + } +} + diff --git a/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).types b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).types new file mode 100644 index 00000000000..10e631e6c93 --- /dev/null +++ b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=false).types @@ -0,0 +1,55 @@ +=== tests/cases/conformance/types/typeRelationships/comparable/equalityWithtNullishCoalescingAssignment.ts === +function f1(a?: boolean): void { +>f1 : (a?: boolean) => void +>a : boolean + + a ??= true; +>a ??= true : boolean +>a : boolean +>true : true + + if (a === false) { +>a === false : boolean +>a : boolean +>false : false + + console.log(a); +>console.log(a) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>a : false + } +} +f1(false); +>f1(false) : void +>f1 : (a?: boolean) => void +>false : false + +function f2() { +>f2 : () => void + + let x: 0 | 1 | 2 | 3 = 0 as any; +>x : 0 | 1 | 2 | 3 +>0 as any : any +>0 : 0 + + x ??= 1; +>x ??= 1 : 0 | 1 | 2 | 3 +>x : 0 | 1 | 2 | 3 +>1 : 1 + + if (x === 0) { +>x === 0 : boolean +>x : 0 | 1 | 2 | 3 +>0 : 0 + + console.log(x); +>console.log(x) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>x : 0 + } +} + diff --git a/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).js b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).js new file mode 100644 index 00000000000..74d557fcebc --- /dev/null +++ b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).js @@ -0,0 +1,35 @@ +//// [equalityWithtNullishCoalescingAssignment.ts] +function f1(a?: boolean): void { + a ??= true; + + if (a === false) { + console.log(a); + } +} +f1(false); + +function f2() { + let x: 0 | 1 | 2 | 3 = 0 as any; + x ??= 1; + if (x === 0) { + console.log(x); + } +} + + +//// [equalityWithtNullishCoalescingAssignment.js] +"use strict"; +function f1(a) { + a !== null && a !== void 0 ? a : (a = true); + if (a === false) { + console.log(a); + } +} +f1(false); +function f2() { + var x = 0; + x !== null && x !== void 0 ? x : (x = 1); + if (x === 0) { + console.log(x); + } +} diff --git a/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).symbols b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).symbols new file mode 100644 index 00000000000..7377bc3dd55 --- /dev/null +++ b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).symbols @@ -0,0 +1,41 @@ +=== tests/cases/conformance/types/typeRelationships/comparable/equalityWithtNullishCoalescingAssignment.ts === +function f1(a?: boolean): void { +>f1 : Symbol(f1, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 0)) +>a : Symbol(a, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 12)) + + a ??= true; +>a : Symbol(a, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 12)) + + if (a === false) { +>a : Symbol(a, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 12)) + + console.log(a); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>a : Symbol(a, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 12)) + } +} +f1(false); +>f1 : Symbol(f1, Decl(equalityWithtNullishCoalescingAssignment.ts, 0, 0)) + +function f2() { +>f2 : Symbol(f2, Decl(equalityWithtNullishCoalescingAssignment.ts, 7, 10)) + + let x: 0 | 1 | 2 | 3 = 0 as any; +>x : Symbol(x, Decl(equalityWithtNullishCoalescingAssignment.ts, 10, 7)) + + x ??= 1; +>x : Symbol(x, Decl(equalityWithtNullishCoalescingAssignment.ts, 10, 7)) + + if (x === 0) { +>x : Symbol(x, Decl(equalityWithtNullishCoalescingAssignment.ts, 10, 7)) + + console.log(x); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>x : Symbol(x, Decl(equalityWithtNullishCoalescingAssignment.ts, 10, 7)) + } +} + diff --git a/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).types b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).types new file mode 100644 index 00000000000..04f6e91175a --- /dev/null +++ b/tests/baselines/reference/equalityWithtNullishCoalescingAssignment(strict=true).types @@ -0,0 +1,55 @@ +=== tests/cases/conformance/types/typeRelationships/comparable/equalityWithtNullishCoalescingAssignment.ts === +function f1(a?: boolean): void { +>f1 : (a?: boolean) => void +>a : boolean | undefined + + a ??= true; +>a ??= true : boolean +>a : boolean | undefined +>true : true + + if (a === false) { +>a === false : boolean +>a : boolean +>false : false + + console.log(a); +>console.log(a) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>a : false + } +} +f1(false); +>f1(false) : void +>f1 : (a?: boolean | undefined) => void +>false : false + +function f2() { +>f2 : () => void + + let x: 0 | 1 | 2 | 3 = 0 as any; +>x : 0 | 1 | 2 | 3 +>0 as any : any +>0 : 0 + + x ??= 1; +>x ??= 1 : 0 | 1 | 2 | 3 +>x : 0 | 1 | 2 | 3 +>1 : 1 + + if (x === 0) { +>x === 0 : boolean +>x : 0 | 1 | 2 | 3 +>0 : 0 + + console.log(x); +>console.log(x) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>x : 0 + } +} + diff --git a/tests/cases/conformance/types/typeRelationships/comparable/equalityWithtNullishCoalescingAssignment.ts b/tests/cases/conformance/types/typeRelationships/comparable/equalityWithtNullishCoalescingAssignment.ts new file mode 100644 index 00000000000..fdcfa38d2f5 --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/comparable/equalityWithtNullishCoalescingAssignment.ts @@ -0,0 +1,18 @@ +// @strict: true, false + +function f1(a?: boolean): void { + a ??= true; + + if (a === false) { + console.log(a); + } +} +f1(false); + +function f2() { + let x: 0 | 1 | 2 | 3 = 0 as any; + x ??= 1; + if (x === 0) { + console.log(x); + } +}