From 750fd41887722e4b7d3cd705392706a4c275c96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 9 Apr 2023 09:36:32 +0200 Subject: [PATCH 1/5] Narrow by comparisons to boolean literals --- src/compiler/binder.ts | 4 +- src/compiler/checker.ts | 13 ++ .../narrowByBooleanComparison.symbols | 98 +++++++++++++++ .../reference/narrowByBooleanComparison.types | 118 ++++++++++++++++++ .../compiler/narrowByBooleanComparison.ts | 43 +++++++ 5 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/narrowByBooleanComparison.symbols create mode 100644 tests/baselines/reference/narrowByBooleanComparison.types create mode 100644 tests/cases/compiler/narrowByBooleanComparison.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 48836b20b04c6..dd0dca995a6cd 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -134,6 +134,7 @@ import { isBlock, isBlockOrCatchScoped, IsBlockScopedContainer, + isBooleanLiteral, isCallExpression, isClassStaticBlockDeclaration, isConditionalTypeNode, @@ -1260,7 +1261,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) || - isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); + isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right) || + (isBooleanLiteral(expr.right) && isNarrowingExpression(expr.left) || isBooleanLiteral(expr.left) && isNarrowingExpression(expr.right)); case SyntaxKind.InstanceOfKeyword: return isNarrowableOperand(expr.left); case SyntaxKind.InKeyword: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d4137af578d98..7edacfb78a8f3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33,6 +33,7 @@ import { BigIntLiteral, BigIntLiteralType, BinaryExpression, + BinaryOperator, BinaryOperatorToken, binarySearch, BindableObjectDefinePropertyCall, @@ -43,6 +44,7 @@ import { BindingPattern, bindSourceFile, Block, + BooleanLiteral, BreakOrContinueStatement, CallChain, CallExpression, @@ -26992,6 +26994,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } + function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type { + assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken); + return narrowType(type, expr, assumeTrue); + } + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: @@ -27012,6 +27019,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue); } + if (isBooleanLiteral(right)) { + return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); + } + if (isBooleanLiteral(left)) { + return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); + } if (isMatchingReference(reference, left)) { return narrowTypeByEquality(type, operator, right, assumeTrue); } diff --git a/tests/baselines/reference/narrowByBooleanComparison.symbols b/tests/baselines/reference/narrowByBooleanComparison.symbols new file mode 100644 index 0000000000000..7828f90242892 --- /dev/null +++ b/tests/baselines/reference/narrowByBooleanComparison.symbols @@ -0,0 +1,98 @@ +=== tests/cases/compiler/narrowByBooleanComparison.ts === +type A = { type: "A" }; +>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0)) +>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10)) + +type B = { type: "B" }; +>B : Symbol(B, Decl(narrowByBooleanComparison.ts, 0, 23)) +>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 1, 10)) + +type C = { type: "C" }; +>C : Symbol(C, Decl(narrowByBooleanComparison.ts, 1, 23)) +>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 2, 10)) + +type MyUnion = A | B | C; +>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23)) +>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0)) +>B : Symbol(B, Decl(narrowByBooleanComparison.ts, 0, 23)) +>C : Symbol(C, Decl(narrowByBooleanComparison.ts, 1, 23)) + +const isA = (x: MyUnion): x is A => x.type === "A"; +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13)) +>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13)) +>A : Symbol(A, Decl(narrowByBooleanComparison.ts, 0, 0)) +>x.type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10), Decl(narrowByBooleanComparison.ts, 1, 10), Decl(narrowByBooleanComparison.ts, 2, 10)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 5, 13)) +>type : Symbol(type, Decl(narrowByBooleanComparison.ts, 0, 10), Decl(narrowByBooleanComparison.ts, 1, 10), Decl(narrowByBooleanComparison.ts, 2, 10)) + +function test1(x: MyUnion) { +>test1 : Symbol(test1, Decl(narrowByBooleanComparison.ts, 5, 51)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) +>MyUnion : Symbol(MyUnion, Decl(narrowByBooleanComparison.ts, 2, 23)) + + if (isA(x) !== true) { +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + + x +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + } + + if (isA(x) !== false) { +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + + x +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + } + + if (isA(x) === false) { +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + + x +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + } + + if (isA(x) === true) { +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + + x +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + } + + if (isA(x) != true) { +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + + x +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + } + + if (isA(x) == true) { +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + + x +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + } + + if (true !== isA(x)) { +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + + x +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + } + + if (true === isA(x)) { +>isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + + x +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) + } +} diff --git a/tests/baselines/reference/narrowByBooleanComparison.types b/tests/baselines/reference/narrowByBooleanComparison.types new file mode 100644 index 0000000000000..fddcc321522d4 --- /dev/null +++ b/tests/baselines/reference/narrowByBooleanComparison.types @@ -0,0 +1,118 @@ +=== tests/cases/compiler/narrowByBooleanComparison.ts === +type A = { type: "A" }; +>A : { type: "A"; } +>type : "A" + +type B = { type: "B" }; +>B : { type: "B"; } +>type : "B" + +type C = { type: "C" }; +>C : { type: "C"; } +>type : "C" + +type MyUnion = A | B | C; +>MyUnion : A | B | C + +const isA = (x: MyUnion): x is A => x.type === "A"; +>isA : (x: MyUnion) => x is A +>(x: MyUnion): x is A => x.type === "A" : (x: MyUnion) => x is A +>x : MyUnion +>x.type === "A" : boolean +>x.type : "A" | "B" | "C" +>x : MyUnion +>type : "A" | "B" | "C" +>"A" : "A" + +function test1(x: MyUnion) { +>test1 : (x: MyUnion) => void +>x : MyUnion + + if (isA(x) !== true) { +>isA(x) !== true : boolean +>isA(x) : boolean +>isA : (x: MyUnion) => x is A +>x : MyUnion +>true : true + + x +>x : B | C + } + + if (isA(x) !== false) { +>isA(x) !== false : boolean +>isA(x) : boolean +>isA : (x: MyUnion) => x is A +>x : MyUnion +>false : false + + x +>x : A + } + + if (isA(x) === false) { +>isA(x) === false : boolean +>isA(x) : boolean +>isA : (x: MyUnion) => x is A +>x : MyUnion +>false : false + + x +>x : B | C + } + + if (isA(x) === true) { +>isA(x) === true : boolean +>isA(x) : boolean +>isA : (x: MyUnion) => x is A +>x : MyUnion +>true : true + + x +>x : A + } + + if (isA(x) != true) { +>isA(x) != true : boolean +>isA(x) : boolean +>isA : (x: MyUnion) => x is A +>x : MyUnion +>true : true + + x +>x : B | C + } + + if (isA(x) == true) { +>isA(x) == true : boolean +>isA(x) : boolean +>isA : (x: MyUnion) => x is A +>x : MyUnion +>true : true + + x +>x : A + } + + if (true !== isA(x)) { +>true !== isA(x) : boolean +>true : true +>isA(x) : boolean +>isA : (x: MyUnion) => x is A +>x : MyUnion + + x +>x : B | C + } + + if (true === isA(x)) { +>true === isA(x) : boolean +>true : true +>isA(x) : boolean +>isA : (x: MyUnion) => x is A +>x : MyUnion + + x +>x : A + } +} diff --git a/tests/cases/compiler/narrowByBooleanComparison.ts b/tests/cases/compiler/narrowByBooleanComparison.ts new file mode 100644 index 0000000000000..b8a0053e038e2 --- /dev/null +++ b/tests/cases/compiler/narrowByBooleanComparison.ts @@ -0,0 +1,43 @@ +// @strict: true +// @noEmit: true + +type A = { type: "A" }; +type B = { type: "B" }; +type C = { type: "C" }; +type MyUnion = A | B | C; + +const isA = (x: MyUnion): x is A => x.type === "A"; + +function test1(x: MyUnion) { + if (isA(x) !== true) { + x + } + + if (isA(x) !== false) { + x + } + + if (isA(x) === false) { + x + } + + if (isA(x) === true) { + x + } + + if (isA(x) != true) { + x + } + + if (isA(x) == true) { + x + } + + if (true !== isA(x)) { + x + } + + if (true === isA(x)) { + x + } +} \ No newline at end of file From 2540cc44e1032c3a993f6073b93de22de29a7517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 9 Apr 2023 23:56:09 +0200 Subject: [PATCH 2/5] few more tests --- .../narrowByBooleanComparison.symbols | 51 ++++++++++++--- .../reference/narrowByBooleanComparison.types | 63 ++++++++++++++++--- .../compiler/narrowByBooleanComparison.ts | 34 +++++++--- 3 files changed, 123 insertions(+), 25 deletions(-) diff --git a/tests/baselines/reference/narrowByBooleanComparison.symbols b/tests/baselines/reference/narrowByBooleanComparison.symbols index 7828f90242892..4e706c5a0bca8 100644 --- a/tests/baselines/reference/narrowByBooleanComparison.symbols +++ b/tests/baselines/reference/narrowByBooleanComparison.symbols @@ -36,7 +36,7 @@ function test1(x: MyUnion) { >isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) - x + x; >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) } @@ -44,7 +44,7 @@ function test1(x: MyUnion) { >isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) - x + x; >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) } @@ -52,7 +52,7 @@ function test1(x: MyUnion) { >isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) - x + x; >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) } @@ -60,7 +60,7 @@ function test1(x: MyUnion) { >isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) - x + x; >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) } @@ -68,7 +68,7 @@ function test1(x: MyUnion) { >isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) - x + x; >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) } @@ -76,7 +76,7 @@ function test1(x: MyUnion) { >isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) - x + x; >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) } @@ -84,7 +84,7 @@ function test1(x: MyUnion) { >isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) - x + x; >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) } @@ -92,7 +92,42 @@ function test1(x: MyUnion) { >isA : Symbol(isA, Decl(narrowByBooleanComparison.ts, 5, 5)) >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) - x + x; >x : Symbol(x, Decl(narrowByBooleanComparison.ts, 7, 15)) } } + +// https://github.com/microsoft/TypeScript/issues/53093 +function test2(x: unknown) { +>test2 : Symbol(test2, Decl(narrowByBooleanComparison.ts, 39, 1)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 42, 15)) + + if (x instanceof Error === false) { +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 42, 15)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + return; + } + x; +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 42, 15)) +} + +// https://github.com/microsoft/TypeScript/issues/50712 +function test3(foo: unknown) { +>test3 : Symbol(test3, Decl(narrowByBooleanComparison.ts, 47, 1)) +>foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15)) + + if (typeof foo !== 'string' && Array.isArray(foo) === false) { +>foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15)) +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15)) + + throw new Error('Not a string or an array'); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + foo; +>foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15)) +} + diff --git a/tests/baselines/reference/narrowByBooleanComparison.types b/tests/baselines/reference/narrowByBooleanComparison.types index fddcc321522d4..b0e27218cb355 100644 --- a/tests/baselines/reference/narrowByBooleanComparison.types +++ b/tests/baselines/reference/narrowByBooleanComparison.types @@ -35,7 +35,7 @@ function test1(x: MyUnion) { >x : MyUnion >true : true - x + x; >x : B | C } @@ -46,7 +46,7 @@ function test1(x: MyUnion) { >x : MyUnion >false : false - x + x; >x : A } @@ -57,7 +57,7 @@ function test1(x: MyUnion) { >x : MyUnion >false : false - x + x; >x : B | C } @@ -68,7 +68,7 @@ function test1(x: MyUnion) { >x : MyUnion >true : true - x + x; >x : A } @@ -79,7 +79,7 @@ function test1(x: MyUnion) { >x : MyUnion >true : true - x + x; >x : B | C } @@ -90,7 +90,7 @@ function test1(x: MyUnion) { >x : MyUnion >true : true - x + x; >x : A } @@ -101,7 +101,7 @@ function test1(x: MyUnion) { >isA : (x: MyUnion) => x is A >x : MyUnion - x + x; >x : B | C } @@ -112,7 +112,54 @@ function test1(x: MyUnion) { >isA : (x: MyUnion) => x is A >x : MyUnion - x + x; >x : A } } + +// https://github.com/microsoft/TypeScript/issues/53093 +function test2(x: unknown) { +>test2 : (x: unknown) => void +>x : unknown + + if (x instanceof Error === false) { +>x instanceof Error === false : boolean +>x instanceof Error : boolean +>x : unknown +>Error : ErrorConstructor +>false : false + + return; + } + x; +>x : Error +} + +// https://github.com/microsoft/TypeScript/issues/50712 +function test3(foo: unknown) { +>test3 : (foo: unknown) => void +>foo : unknown + + if (typeof foo !== 'string' && Array.isArray(foo) === false) { +>typeof foo !== 'string' && Array.isArray(foo) === false : boolean +>typeof foo !== 'string' : boolean +>typeof foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>foo : unknown +>'string' : "string" +>Array.isArray(foo) === false : boolean +>Array.isArray(foo) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>foo : unknown +>false : false + + throw new Error('Not a string or an array'); +>new Error('Not a string or an array') : Error +>Error : ErrorConstructor +>'Not a string or an array' : "Not a string or an array" + } + foo; +>foo : string | any[] +} + diff --git a/tests/cases/compiler/narrowByBooleanComparison.ts b/tests/cases/compiler/narrowByBooleanComparison.ts index b8a0053e038e2..5d3dcbba00d98 100644 --- a/tests/cases/compiler/narrowByBooleanComparison.ts +++ b/tests/cases/compiler/narrowByBooleanComparison.ts @@ -10,34 +10,50 @@ const isA = (x: MyUnion): x is A => x.type === "A"; function test1(x: MyUnion) { if (isA(x) !== true) { - x + x; } if (isA(x) !== false) { - x + x; } if (isA(x) === false) { - x + x; } if (isA(x) === true) { - x + x; } if (isA(x) != true) { - x + x; } if (isA(x) == true) { - x + x; } if (true !== isA(x)) { - x + x; } if (true === isA(x)) { - x + x; } -} \ No newline at end of file +} + +// https://github.com/microsoft/TypeScript/issues/53093 +function test2(x: unknown) { + if (x instanceof Error === false) { + return; + } + x; +} + +// https://github.com/microsoft/TypeScript/issues/50712 +function test3(foo: unknown) { + if (typeof foo !== 'string' && Array.isArray(foo) === false) { + throw new Error('Not a string or an array'); + } + foo; +} From a27a5e6eed7c00769c1dda74407997efe5344974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 10 Apr 2023 09:10:22 +0200 Subject: [PATCH 3/5] Move `narrowTypeByEquality` before `narrowTypeByBooleanComparison` --- src/compiler/checker.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7edacfb78a8f3..439ed54ac2865 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27019,18 +27019,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue); } - if (isBooleanLiteral(right)) { - return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); - } - if (isBooleanLiteral(left)) { - return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); - } if (isMatchingReference(reference, left)) { return narrowTypeByEquality(type, operator, right, assumeTrue); } if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } + if (isBooleanLiteral(right)) { + return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); + } + if (isBooleanLiteral(left)) { + return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); + } if (strictNullChecks) { if (optionalChainContainsReference(left, reference)) { type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); From 586003bde89225091155f5ced76f7260dea488b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 10 Apr 2023 09:12:20 +0200 Subject: [PATCH 4/5] Make `narrowTypeByBooleanComparison` last --- src/compiler/checker.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 439ed54ac2865..9edc76ab6e9e2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27025,12 +27025,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (isBooleanLiteral(right)) { - return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); - } - if (isBooleanLiteral(left)) { - return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); - } if (strictNullChecks) { if (optionalChainContainsReference(left, reference)) { type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); @@ -27053,6 +27047,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isMatchingConstructorReference(right)) { return narrowTypeByConstructor(type, operator, left, assumeTrue); } + if (isBooleanLiteral(right)) { + return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); + } + if (isBooleanLiteral(left)) { + return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); + } break; case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); From 9568e9e36a635782114c9072a66e5ca81440d4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 11 Sep 2023 14:08:16 +0200 Subject: [PATCH 5/5] add more issues-based tests --- .../narrowByBooleanComparison.symbols | 129 ++++++++++++++++ .../reference/narrowByBooleanComparison.types | 141 ++++++++++++++++++ .../compiler/narrowByBooleanComparison.ts | 54 +++++++ 3 files changed, 324 insertions(+) diff --git a/tests/baselines/reference/narrowByBooleanComparison.symbols b/tests/baselines/reference/narrowByBooleanComparison.symbols index 926cf20ba6182..466d5936028bd 100644 --- a/tests/baselines/reference/narrowByBooleanComparison.symbols +++ b/tests/baselines/reference/narrowByBooleanComparison.symbols @@ -133,3 +133,132 @@ function test3(foo: unknown) { >foo : Symbol(foo, Decl(narrowByBooleanComparison.ts, 50, 15)) } +// https://github.com/microsoft/TypeScript/issues/55395 +class WebError extends URIError { +>WebError : Symbol(WebError, Decl(narrowByBooleanComparison.ts, 55, 1)) +>URIError : Symbol(URIError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + status?: number; +>status : Symbol(WebError.status, Decl(narrowByBooleanComparison.ts, 58, 33)) +} +function test4() { +>test4 : Symbol(test4, Decl(narrowByBooleanComparison.ts, 60, 1)) + + try { + // make a request + } catch (err) { +>err : Symbol(err, Decl(narrowByBooleanComparison.ts, 64, 13)) + + if (err instanceof WebError === false || err.status != 401) { +>err : Symbol(err, Decl(narrowByBooleanComparison.ts, 64, 13)) +>WebError : Symbol(WebError, Decl(narrowByBooleanComparison.ts, 55, 1)) +>err.status : Symbol(WebError.status, Decl(narrowByBooleanComparison.ts, 58, 33)) +>err : Symbol(err, Decl(narrowByBooleanComparison.ts, 64, 13)) +>status : Symbol(WebError.status, Decl(narrowByBooleanComparison.ts, 58, 33)) + + console.error(err); +>console.error : Symbol(Console.error, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>error : Symbol(Console.error, Decl(lib.dom.d.ts, --, --)) +>err : Symbol(err, Decl(narrowByBooleanComparison.ts, 64, 13)) + } + } +} + +// https://github.com/microsoft/TypeScript/issues/44366 +interface Entity { +>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1)) + + type: string; +>type : Symbol(Entity.type, Decl(narrowByBooleanComparison.ts, 72, 18)) +} +const ACTOR_TYPE = "actor"; +>ACTOR_TYPE : Symbol(ACTOR_TYPE, Decl(narrowByBooleanComparison.ts, 75, 5)) + +interface Actor extends Entity { +>Actor : Symbol(Actor, Decl(narrowByBooleanComparison.ts, 75, 27)) +>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1)) + + type: typeof ACTOR_TYPE; +>type : Symbol(Actor.type, Decl(narrowByBooleanComparison.ts, 76, 32)) +>ACTOR_TYPE : Symbol(ACTOR_TYPE, Decl(narrowByBooleanComparison.ts, 75, 5)) +} +function isActor(entity: Entity): entity is Actor { +>isActor : Symbol(isActor, Decl(narrowByBooleanComparison.ts, 78, 1)) +>entity : Symbol(entity, Decl(narrowByBooleanComparison.ts, 79, 17)) +>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1)) +>entity : Symbol(entity, Decl(narrowByBooleanComparison.ts, 79, 17)) +>Actor : Symbol(Actor, Decl(narrowByBooleanComparison.ts, 75, 27)) + + return entity.type === ACTOR_TYPE; +>entity.type : Symbol(Entity.type, Decl(narrowByBooleanComparison.ts, 72, 18)) +>entity : Symbol(entity, Decl(narrowByBooleanComparison.ts, 79, 17)) +>type : Symbol(Entity.type, Decl(narrowByBooleanComparison.ts, 72, 18)) +>ACTOR_TYPE : Symbol(ACTOR_TYPE, Decl(narrowByBooleanComparison.ts, 75, 5)) +} +function test5(bin: Entity) { +>test5 : Symbol(test5, Decl(narrowByBooleanComparison.ts, 81, 1)) +>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 82, 15)) +>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1)) + + if (isActor(bin) === false) { +>isActor : Symbol(isActor, Decl(narrowByBooleanComparison.ts, 78, 1)) +>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 82, 15)) + + bin; +>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 82, 15)) + + } else { + bin; +>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 82, 15)) + } +} +function test6(bin: Entity) { +>test6 : Symbol(test6, Decl(narrowByBooleanComparison.ts, 88, 1)) +>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 89, 15)) +>Entity : Symbol(Entity, Decl(narrowByBooleanComparison.ts, 69, 1)) + + if (isActor(bin) == false) { +>isActor : Symbol(isActor, Decl(narrowByBooleanComparison.ts, 78, 1)) +>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 89, 15)) + + bin; +>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 89, 15)) + + } else { + bin; +>bin : Symbol(bin, Decl(narrowByBooleanComparison.ts, 89, 15)) + } +} + +// https://github.com/microsoft/TypeScript/issues/53005 +function isFunction(x: unknown): x is Function { +>isFunction : Symbol(isFunction, Decl(narrowByBooleanComparison.ts, 95, 1)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 98, 20)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 98, 20)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + return typeof x === "function"; +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 98, 20)) +} + +function test7(x: unknown) { +>test7 : Symbol(test7, Decl(narrowByBooleanComparison.ts, 100, 1)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15)) + + if (isFunction(x) !== false) { +>isFunction : Symbol(isFunction, Decl(narrowByBooleanComparison.ts, 95, 1)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15)) + + x; +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15)) + } + if (isFunction(x) === true) { +>isFunction : Symbol(isFunction, Decl(narrowByBooleanComparison.ts, 95, 1)) +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15)) + + x; +>x : Symbol(x, Decl(narrowByBooleanComparison.ts, 102, 15)) + } +} + diff --git a/tests/baselines/reference/narrowByBooleanComparison.types b/tests/baselines/reference/narrowByBooleanComparison.types index 36ea729065d63..a21e39c92f7fb 100644 --- a/tests/baselines/reference/narrowByBooleanComparison.types +++ b/tests/baselines/reference/narrowByBooleanComparison.types @@ -165,3 +165,144 @@ function test3(foo: unknown) { >foo : string | any[] } +// https://github.com/microsoft/TypeScript/issues/55395 +class WebError extends URIError { +>WebError : WebError +>URIError : URIError + + status?: number; +>status : number | undefined +} +function test4() { +>test4 : () => void + + try { + // make a request + } catch (err) { +>err : unknown + + if (err instanceof WebError === false || err.status != 401) { +>err instanceof WebError === false || err.status != 401 : boolean +>err instanceof WebError === false : boolean +>err instanceof WebError : boolean +>err : unknown +>WebError : typeof WebError +>false : false +>err.status != 401 : boolean +>err.status : number | undefined +>err : WebError +>status : number | undefined +>401 : 401 + + console.error(err); +>console.error(err) : void +>console.error : (...data: any[]) => void +>console : Console +>error : (...data: any[]) => void +>err : unknown + } + } +} + +// https://github.com/microsoft/TypeScript/issues/44366 +interface Entity { + type: string; +>type : string +} +const ACTOR_TYPE = "actor"; +>ACTOR_TYPE : "actor" +>"actor" : "actor" + +interface Actor extends Entity { + type: typeof ACTOR_TYPE; +>type : "actor" +>ACTOR_TYPE : "actor" +} +function isActor(entity: Entity): entity is Actor { +>isActor : (entity: Entity) => entity is Actor +>entity : Entity + + return entity.type === ACTOR_TYPE; +>entity.type === ACTOR_TYPE : boolean +>entity.type : string +>entity : Entity +>type : string +>ACTOR_TYPE : "actor" +} +function test5(bin: Entity) { +>test5 : (bin: Entity) => void +>bin : Entity + + if (isActor(bin) === false) { +>isActor(bin) === false : boolean +>isActor(bin) : boolean +>isActor : (entity: Entity) => entity is Actor +>bin : Entity +>false : false + + bin; +>bin : Entity + + } else { + bin; +>bin : Actor + } +} +function test6(bin: Entity) { +>test6 : (bin: Entity) => void +>bin : Entity + + if (isActor(bin) == false) { +>isActor(bin) == false : boolean +>isActor(bin) : boolean +>isActor : (entity: Entity) => entity is Actor +>bin : Entity +>false : false + + bin; +>bin : Entity + + } else { + bin; +>bin : Actor + } +} + +// https://github.com/microsoft/TypeScript/issues/53005 +function isFunction(x: unknown): x is Function { +>isFunction : (x: unknown) => x is Function +>x : unknown + + return typeof x === "function"; +>typeof x === "function" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : unknown +>"function" : "function" +} + +function test7(x: unknown) { +>test7 : (x: unknown) => void +>x : unknown + + if (isFunction(x) !== false) { +>isFunction(x) !== false : boolean +>isFunction(x) : boolean +>isFunction : (x: unknown) => x is Function +>x : unknown +>false : false + + x; +>x : Function + } + if (isFunction(x) === true) { +>isFunction(x) === true : boolean +>isFunction(x) : boolean +>isFunction : (x: unknown) => x is Function +>x : unknown +>true : true + + x; +>x : Function + } +} + diff --git a/tests/cases/compiler/narrowByBooleanComparison.ts b/tests/cases/compiler/narrowByBooleanComparison.ts index 5d3dcbba00d98..6dbb34a4055a0 100644 --- a/tests/cases/compiler/narrowByBooleanComparison.ts +++ b/tests/cases/compiler/narrowByBooleanComparison.ts @@ -57,3 +57,57 @@ function test3(foo: unknown) { } foo; } + +// https://github.com/microsoft/TypeScript/issues/55395 +class WebError extends URIError { + status?: number; +} +function test4() { + try { + // make a request + } catch (err) { + if (err instanceof WebError === false || err.status != 401) { + console.error(err); + } + } +} + +// https://github.com/microsoft/TypeScript/issues/44366 +interface Entity { + type: string; +} +const ACTOR_TYPE = "actor"; +interface Actor extends Entity { + type: typeof ACTOR_TYPE; +} +function isActor(entity: Entity): entity is Actor { + return entity.type === ACTOR_TYPE; +} +function test5(bin: Entity) { + if (isActor(bin) === false) { + bin; + } else { + bin; + } +} +function test6(bin: Entity) { + if (isActor(bin) == false) { + bin; + } else { + bin; + } +} + +// https://github.com/microsoft/TypeScript/issues/53005 +function isFunction(x: unknown): x is Function { + return typeof x === "function"; +} + +function test7(x: unknown) { + if (isFunction(x) !== false) { + x; + } + if (isFunction(x) === true) { + x; + } +}