From c14d573cc3b70871a7fd12381bbc45d9911da9c8 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 22 Jul 2021 10:54:29 -0700 Subject: [PATCH 1/3] allow narrowing for any left-hand in operand --- src/compiler/binder.ts | 6 +- .../reference/controlFlowForInStatement2.js | 37 +++++++++++++ .../controlFlowForInStatement2.symbols | 48 ++++++++++++++++ .../controlFlowForInStatement2.types | 55 +++++++++++++++++++ .../controlFlow/controlFlowForInStatement2.ts | 21 +++++++ 5 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/controlFlowForInStatement2.js create mode 100644 tests/baselines/reference/controlFlowForInStatement2.symbols create mode 100644 tests/baselines/reference/controlFlowForInStatement2.types create mode 100644 tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5ba3999e1a82d..04ce9e54983e3 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -916,8 +916,8 @@ namespace ts { return isTypeOfExpression(expr1) && isNarrowableOperand(expr1.expression) && isStringLiteralLike(expr2); } - function isNarrowableInOperands(left: Expression, right: Expression) { - return isNarrowingExpression(right) && (isIdentifier(left) || isStringLiteralLike(left)); + function isNarrowableInRightOperand(expr: Expression) { + return isNarrowingExpression(expr); } function isNarrowingBinaryExpression(expr: BinaryExpression) { @@ -936,7 +936,7 @@ namespace ts { case SyntaxKind.InstanceOfKeyword: return isNarrowableOperand(expr.left); case SyntaxKind.InKeyword: - return isNarrowableInOperands(expr.left, expr.right); + return isNarrowableInRightOperand(expr.right); case SyntaxKind.CommaToken: return isNarrowingExpression(expr.right); } diff --git a/tests/baselines/reference/controlFlowForInStatement2.js b/tests/baselines/reference/controlFlowForInStatement2.js new file mode 100644 index 0000000000000..625af9c3702ac --- /dev/null +++ b/tests/baselines/reference/controlFlowForInStatement2.js @@ -0,0 +1,37 @@ +//// [controlFlowForInStatement2.ts] +const keywordA = 'a'; +const keywordB = 'b'; + +type A = { [keywordA]: number }; +type B = { [keywordB]: string }; + +declare const c: A | B; + +if ('a' in c) { + c; // narrowed to `A` +} + +if (keywordA in c) { + c; // also narrowed to `A` +} + +let stringB: string = 'b'; + +if ((stringB as 'b') in c) { + c; // narrowed to `B` +} + + +//// [controlFlowForInStatement2.js] +var keywordA = 'a'; +var keywordB = 'b'; +if ('a' in c) { + c; // narrowed to `A` +} +if (keywordA in c) { + c; // also narrowed to `A` +} +var stringB = 'b'; +if (stringB in c) { + c; // narrowed to `B` +} diff --git a/tests/baselines/reference/controlFlowForInStatement2.symbols b/tests/baselines/reference/controlFlowForInStatement2.symbols new file mode 100644 index 0000000000000..4644fe67fefdb --- /dev/null +++ b/tests/baselines/reference/controlFlowForInStatement2.symbols @@ -0,0 +1,48 @@ +=== tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts === +const keywordA = 'a'; +>keywordA : Symbol(keywordA, Decl(controlFlowForInStatement2.ts, 0, 5)) + +const keywordB = 'b'; +>keywordB : Symbol(keywordB, Decl(controlFlowForInStatement2.ts, 1, 5)) + +type A = { [keywordA]: number }; +>A : Symbol(A, Decl(controlFlowForInStatement2.ts, 1, 21)) +>[keywordA] : Symbol([keywordA], Decl(controlFlowForInStatement2.ts, 3, 10)) +>keywordA : Symbol(keywordA, Decl(controlFlowForInStatement2.ts, 0, 5)) + +type B = { [keywordB]: string }; +>B : Symbol(B, Decl(controlFlowForInStatement2.ts, 3, 32)) +>[keywordB] : Symbol([keywordB], Decl(controlFlowForInStatement2.ts, 4, 10)) +>keywordB : Symbol(keywordB, Decl(controlFlowForInStatement2.ts, 1, 5)) + +declare const c: A | B; +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) +>A : Symbol(A, Decl(controlFlowForInStatement2.ts, 1, 21)) +>B : Symbol(B, Decl(controlFlowForInStatement2.ts, 3, 32)) + +if ('a' in c) { +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) + + c; // narrowed to `A` +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) +} + +if (keywordA in c) { +>keywordA : Symbol(keywordA, Decl(controlFlowForInStatement2.ts, 0, 5)) +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) + + c; // also narrowed to `A` +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) +} + +let stringB: string = 'b'; +>stringB : Symbol(stringB, Decl(controlFlowForInStatement2.ts, 16, 3)) + +if ((stringB as 'b') in c) { +>stringB : Symbol(stringB, Decl(controlFlowForInStatement2.ts, 16, 3)) +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) + + c; // narrowed to `B` +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) +} + diff --git a/tests/baselines/reference/controlFlowForInStatement2.types b/tests/baselines/reference/controlFlowForInStatement2.types new file mode 100644 index 0000000000000..a87bbf0f2ed61 --- /dev/null +++ b/tests/baselines/reference/controlFlowForInStatement2.types @@ -0,0 +1,55 @@ +=== tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts === +const keywordA = 'a'; +>keywordA : "a" +>'a' : "a" + +const keywordB = 'b'; +>keywordB : "b" +>'b' : "b" + +type A = { [keywordA]: number }; +>A : A +>[keywordA] : number +>keywordA : "a" + +type B = { [keywordB]: string }; +>B : B +>[keywordB] : string +>keywordB : "b" + +declare const c: A | B; +>c : A | B + +if ('a' in c) { +>'a' in c : boolean +>'a' : "a" +>c : A | B + + c; // narrowed to `A` +>c : A +} + +if (keywordA in c) { +>keywordA in c : boolean +>keywordA : "a" +>c : A | B + + c; // also narrowed to `A` +>c : A +} + +let stringB: string = 'b'; +>stringB : string +>'b' : "b" + +if ((stringB as 'b') in c) { +>(stringB as 'b') in c : boolean +>(stringB as 'b') : "b" +>stringB as 'b' : "b" +>stringB : string +>c : A | B + + c; // narrowed to `B` +>c : B +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts b/tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts new file mode 100644 index 0000000000000..35f20cd166b4a --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts @@ -0,0 +1,21 @@ +const keywordA = 'a'; +const keywordB = 'b'; + +type A = { [keywordA]: number }; +type B = { [keywordB]: string }; + +declare const c: A | B; + +if ('a' in c) { + c; // narrowed to `A` +} + +if (keywordA in c) { + c; // also narrowed to `A` +} + +let stringB: string = 'b'; + +if ((stringB as 'b') in c) { + c; // narrowed to `B` +} From b4f2f8c0d2159172be8263a20f10ff243ebae402 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 22 Jul 2021 11:17:20 -0700 Subject: [PATCH 2/3] update test case --- .../baselines/reference/controlFlowForInStatement2.js | 8 +++++++- .../reference/controlFlowForInStatement2.symbols | 7 +++++++ .../reference/controlFlowForInStatement2.types | 10 ++++++++++ .../controlFlow/controlFlowForInStatement2.ts | 4 ++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/controlFlowForInStatement2.js b/tests/baselines/reference/controlFlowForInStatement2.js index 625af9c3702ac..2ce93a93cd717 100644 --- a/tests/baselines/reference/controlFlowForInStatement2.js +++ b/tests/baselines/reference/controlFlowForInStatement2.js @@ -20,7 +20,10 @@ let stringB: string = 'b'; if ((stringB as 'b') in c) { c; // narrowed to `B` } - + +if ((stringB as ('a' | 'b')) in c) { + c; // not narrowed +} //// [controlFlowForInStatement2.js] var keywordA = 'a'; @@ -35,3 +38,6 @@ var stringB = 'b'; if (stringB in c) { c; // narrowed to `B` } +if (stringB in c) { + c; // not narrowed +} diff --git a/tests/baselines/reference/controlFlowForInStatement2.symbols b/tests/baselines/reference/controlFlowForInStatement2.symbols index 4644fe67fefdb..8277e2352d04f 100644 --- a/tests/baselines/reference/controlFlowForInStatement2.symbols +++ b/tests/baselines/reference/controlFlowForInStatement2.symbols @@ -46,3 +46,10 @@ if ((stringB as 'b') in c) { >c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) } +if ((stringB as ('a' | 'b')) in c) { +>stringB : Symbol(stringB, Decl(controlFlowForInStatement2.ts, 16, 3)) +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) + + c; // not narrowed +>c : Symbol(c, Decl(controlFlowForInStatement2.ts, 6, 13)) +} diff --git a/tests/baselines/reference/controlFlowForInStatement2.types b/tests/baselines/reference/controlFlowForInStatement2.types index a87bbf0f2ed61..2705b8b7d51ed 100644 --- a/tests/baselines/reference/controlFlowForInStatement2.types +++ b/tests/baselines/reference/controlFlowForInStatement2.types @@ -53,3 +53,13 @@ if ((stringB as 'b') in c) { >c : B } +if ((stringB as ('a' | 'b')) in c) { +>(stringB as ('a' | 'b')) in c : boolean +>(stringB as ('a' | 'b')) : "a" | "b" +>stringB as ('a' | 'b') : "a" | "b" +>stringB : string +>c : A | B + + c; // not narrowed +>c : A | B +} diff --git a/tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts b/tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts index 35f20cd166b4a..450e50cadc3db 100644 --- a/tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts +++ b/tests/cases/conformance/controlFlow/controlFlowForInStatement2.ts @@ -19,3 +19,7 @@ let stringB: string = 'b'; if ((stringB as 'b') in c) { c; // narrowed to `B` } + +if ((stringB as ('a' | 'b')) in c) { + c; // not narrowed +} \ No newline at end of file From a02537d9abe2e4ede5c6b4b21f7cfb7402d7a916 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 22 Jul 2021 16:14:11 -0700 Subject: [PATCH 3/3] get rid of useless function --- src/compiler/binder.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 04ce9e54983e3..2887481d78ebb 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -916,10 +916,6 @@ namespace ts { return isTypeOfExpression(expr1) && isNarrowableOperand(expr1.expression) && isStringLiteralLike(expr2); } - function isNarrowableInRightOperand(expr: Expression) { - return isNarrowingExpression(expr); - } - function isNarrowingBinaryExpression(expr: BinaryExpression) { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: @@ -936,7 +932,7 @@ namespace ts { case SyntaxKind.InstanceOfKeyword: return isNarrowableOperand(expr.left); case SyntaxKind.InKeyword: - return isNarrowableInRightOperand(expr.right); + return isNarrowingExpression(expr.right); case SyntaxKind.CommaToken: return isNarrowingExpression(expr.right); }