From 96114887c8f5e5c9947384c0347fcdaa60a2fcbe Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 15 Dec 2021 10:21:10 -0800 Subject: [PATCH 1/5] Detect usage of block-scoped variables from earlier case blocks Fixes #19503 --- src/compiler/checker.ts | 17 ++ src/compiler/diagnosticMessages.json | 5 + .../reference/switchCaseTdz.errors.txt | 126 ++++++++++++++ tests/baselines/reference/switchCaseTdz.js | 111 +++++++++++++ .../baselines/reference/switchCaseTdz.symbols | 125 ++++++++++++++ tests/baselines/reference/switchCaseTdz.types | 157 ++++++++++++++++++ .../unusedSwitchStatement.errors.txt | 5 +- tests/cases/compiler/switchCaseTdz.ts | 56 +++++++ 8 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/switchCaseTdz.errors.txt create mode 100644 tests/baselines/reference/switchCaseTdz.js create mode 100644 tests/baselines/reference/switchCaseTdz.symbols create mode 100644 tests/baselines/reference/switchCaseTdz.types create mode 100644 tests/cases/compiler/switchCaseTdz.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 56a36f658d719..91b6d36eb4cf1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25248,6 +25248,23 @@ namespace ts { if (isCaptured) { getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; } + + // Check for usage of a variable from a case clause prior to the referencing one + // switch(n) { + // case 0: + // let x = 0; + // case 1: + // x; // <- bad + // if (true) x; // <- bad (note: need to walk to parent) + // } + if (container.kind === SyntaxKind.CaseBlock) { + const usageCaseBlock = getAncestor(node, SyntaxKind.CaseClause); + if (usageCaseBlock) { + if (usageCaseBlock.pos > symbol.valueDeclaration.pos) { + error(node, Diagnostics.Variable_0_is_declared_in_a_prior_case_block, symbolToString(symbol)); + } + } + } } function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 778a9304aa75e..8a8bfb1ee0b4e 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3361,6 +3361,11 @@ "category": "Error", "code": 2836 }, + "Variable '{0}' is declared in a prior case block.": { + "category": "Error", + "code": 2837 + }, + "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/switchCaseTdz.errors.txt b/tests/baselines/reference/switchCaseTdz.errors.txt new file mode 100644 index 0000000000000..6658fa3e7c6ac --- /dev/null +++ b/tests/baselines/reference/switchCaseTdz.errors.txt @@ -0,0 +1,126 @@ +tests/cases/compiler/switchCaseTdz.ts(3,9): error TS2448: Block-scoped variable 'x' used before its declaration. +tests/cases/compiler/switchCaseTdz.ts(4,9): error TS2448: Block-scoped variable 'y' used before its declaration. +tests/cases/compiler/switchCaseTdz.ts(5,9): error TS2448: Block-scoped variable 'z' used before its declaration. +tests/cases/compiler/switchCaseTdz.ts(19,9): error TS2837: Variable 'x' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(20,9): error TS2837: Variable 'y' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(21,9): error TS2837: Variable 'z' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(24,13): error TS2837: Variable 'x' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(25,13): error TS2837: Variable 'y' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(26,13): error TS2837: Variable 'z' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(30,13): error TS2837: Variable 'x' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(31,13): error TS2837: Variable 'y' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(32,13): error TS2837: Variable 'z' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(39,9): error TS2837: Variable 'x' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(40,9): error TS2837: Variable 'y' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(41,9): error TS2837: Variable 'z' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(44,13): error TS2837: Variable 'x' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(45,13): error TS2837: Variable 'y' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(46,13): error TS2837: Variable 'z' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(50,13): error TS2837: Variable 'x' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(51,13): error TS2837: Variable 'y' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(52,13): error TS2837: Variable 'z' is declared in a prior case block. + + +==== tests/cases/compiler/switchCaseTdz.ts (21 errors) ==== + switch (1 + 1) { + case -1: + x; + ~ +!!! error TS2448: Block-scoped variable 'x' used before its declaration. +!!! related TS2728 tests/cases/compiler/switchCaseTdz.ts:9:13: 'x' is declared here. + y; + ~ +!!! error TS2448: Block-scoped variable 'y' used before its declaration. +!!! related TS2728 tests/cases/compiler/switchCaseTdz.ts:10:15: 'y' is declared here. + z; + ~ +!!! error TS2448: Block-scoped variable 'z' used before its declaration. +!!! related TS2728 tests/cases/compiler/switchCaseTdz.ts:11:16: 'z' is declared here. + + case 0: + var ok = 0; + let x = 0; + const y = 0; + const [z] = [0]; + + ok; + x; + y; + z; + + case 1: + x = 0; // <- bad + ~ +!!! error TS2837: Variable 'x' is declared in a prior case block. + y; // <- bad + ~ +!!! error TS2837: Variable 'y' is declared in a prior case block. + z; // <- bad + ~ +!!! error TS2837: Variable 'z' is declared in a prior case block. + ok; + if (true) { + x = 0; // <- bad + ~ +!!! error TS2837: Variable 'x' is declared in a prior case block. + y; // <- bad + ~ +!!! error TS2837: Variable 'y' is declared in a prior case block. + z; // <- bad + ~ +!!! error TS2837: Variable 'z' is declared in a prior case block. + ok; + } + let f1 = function () { + x = 0; // <- bad + ~ +!!! error TS2837: Variable 'x' is declared in a prior case block. + y; // <- bad + ~ +!!! error TS2837: Variable 'y' is declared in a prior case block. + z; // <- bad + ~ +!!! error TS2837: Variable 'z' is declared in a prior case block. + ok; + } + break; + + case 2: + case 3: + x; // <- bad + ~ +!!! error TS2837: Variable 'x' is declared in a prior case block. + y; // <- bad + ~ +!!! error TS2837: Variable 'y' is declared in a prior case block. + z; // <- bad + ~ +!!! error TS2837: Variable 'z' is declared in a prior case block. + ok; + if (true) { + x; // <- bad + ~ +!!! error TS2837: Variable 'x' is declared in a prior case block. + y; // <- bad + ~ +!!! error TS2837: Variable 'y' is declared in a prior case block. + z; // <- bad + ~ +!!! error TS2837: Variable 'z' is declared in a prior case block. + ok; + } + let f2 = function () { + x; // <- bad + ~ +!!! error TS2837: Variable 'x' is declared in a prior case block. + y; // <- bad + ~ +!!! error TS2837: Variable 'y' is declared in a prior case block. + z; // <- bad + ~ +!!! error TS2837: Variable 'z' is declared in a prior case block. + ok; + } + + } + \ No newline at end of file diff --git a/tests/baselines/reference/switchCaseTdz.js b/tests/baselines/reference/switchCaseTdz.js new file mode 100644 index 0000000000000..c9b0c0f01a066 --- /dev/null +++ b/tests/baselines/reference/switchCaseTdz.js @@ -0,0 +1,111 @@ +//// [switchCaseTdz.ts] +switch (1 + 1) { + case -1: + x; + y; + z; + + case 0: + var ok = 0; + let x = 0; + const y = 0; + const [z] = [0]; + + ok; + x; + y; + z; + + case 1: + x = 0; // <- bad + y; // <- bad + z; // <- bad + ok; + if (true) { + x = 0; // <- bad + y; // <- bad + z; // <- bad + ok; + } + let f1 = function () { + x = 0; // <- bad + y; // <- bad + z; // <- bad + ok; + } + break; + + case 2: + case 3: + x; // <- bad + y; // <- bad + z; // <- bad + ok; + if (true) { + x; // <- bad + y; // <- bad + z; // <- bad + ok; + } + let f2 = function () { + x; // <- bad + y; // <- bad + z; // <- bad + ok; + } + +} + + +//// [switchCaseTdz.js] +switch (1 + 1) { + case -1: + x_1; + y_1; + z_1; + case 0: + var ok = 0; + var x_1 = 0; + var y_1 = 0; + var z_1 = [0][0]; + ok; + x_1; + y_1; + z_1; + case 1: + x_1 = 0; // <- bad + y_1; // <- bad + z_1; // <- bad + ok; + if (true) { + x_1 = 0; // <- bad + y_1; // <- bad + z_1; // <- bad + ok; + } + var f1 = function () { + x_1 = 0; // <- bad + y_1; // <- bad + z_1; // <- bad + ok; + }; + break; + case 2: + case 3: + x_1; // <- bad + y_1; // <- bad + z_1; // <- bad + ok; + if (true) { + x_1; // <- bad + y_1; // <- bad + z_1; // <- bad + ok; + } + var f2 = function () { + x_1; // <- bad + y_1; // <- bad + z_1; // <- bad + ok; + }; +} diff --git a/tests/baselines/reference/switchCaseTdz.symbols b/tests/baselines/reference/switchCaseTdz.symbols new file mode 100644 index 0000000000000..909baa8159087 --- /dev/null +++ b/tests/baselines/reference/switchCaseTdz.symbols @@ -0,0 +1,125 @@ +=== tests/cases/compiler/switchCaseTdz.ts === +switch (1 + 1) { + case -1: + x; +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + y; +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + z; +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + case 0: + var ok = 0; +>ok : Symbol(ok, Decl(switchCaseTdz.ts, 7, 11)) + + let x = 0; +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + const y = 0; +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + const [z] = [0]; +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + ok; +>ok : Symbol(ok, Decl(switchCaseTdz.ts, 7, 11)) + + x; +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + y; +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + z; +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + case 1: + x = 0; // <- bad +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + y; // <- bad +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + z; // <- bad +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + ok; +>ok : Symbol(ok, Decl(switchCaseTdz.ts, 7, 11)) + + if (true) { + x = 0; // <- bad +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + y; // <- bad +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + z; // <- bad +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + ok; +>ok : Symbol(ok, Decl(switchCaseTdz.ts, 7, 11)) + } + let f1 = function () { +>f1 : Symbol(f1, Decl(switchCaseTdz.ts, 28, 11)) + + x = 0; // <- bad +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + y; // <- bad +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + z; // <- bad +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + ok; +>ok : Symbol(ok, Decl(switchCaseTdz.ts, 7, 11)) + } + break; + + case 2: + case 3: + x; // <- bad +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + y; // <- bad +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + z; // <- bad +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + ok; +>ok : Symbol(ok, Decl(switchCaseTdz.ts, 7, 11)) + + if (true) { + x; // <- bad +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + y; // <- bad +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + z; // <- bad +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + ok; +>ok : Symbol(ok, Decl(switchCaseTdz.ts, 7, 11)) + } + let f2 = function () { +>f2 : Symbol(f2, Decl(switchCaseTdz.ts, 48, 11)) + + x; // <- bad +>x : Symbol(x, Decl(switchCaseTdz.ts, 8, 11)) + + y; // <- bad +>y : Symbol(y, Decl(switchCaseTdz.ts, 9, 13)) + + z; // <- bad +>z : Symbol(z, Decl(switchCaseTdz.ts, 10, 15)) + + ok; +>ok : Symbol(ok, Decl(switchCaseTdz.ts, 7, 11)) + } + +} + diff --git a/tests/baselines/reference/switchCaseTdz.types b/tests/baselines/reference/switchCaseTdz.types new file mode 100644 index 0000000000000..94a50de14aabf --- /dev/null +++ b/tests/baselines/reference/switchCaseTdz.types @@ -0,0 +1,157 @@ +=== tests/cases/compiler/switchCaseTdz.ts === +switch (1 + 1) { +>1 + 1 : number +>1 : 1 +>1 : 1 + + case -1: +>-1 : -1 +>1 : 1 + + x; +>x : number + + y; +>y : 0 + + z; +>z : number + + case 0: +>0 : 0 + + var ok = 0; +>ok : number +>0 : 0 + + let x = 0; +>x : number +>0 : 0 + + const y = 0; +>y : 0 +>0 : 0 + + const [z] = [0]; +>z : number +>[0] : [number] +>0 : 0 + + ok; +>ok : number + + x; +>x : number + + y; +>y : 0 + + z; +>z : number + + case 1: +>1 : 1 + + x = 0; // <- bad +>x = 0 : 0 +>x : number +>0 : 0 + + y; // <- bad +>y : 0 + + z; // <- bad +>z : number + + ok; +>ok : number + + if (true) { +>true : true + + x = 0; // <- bad +>x = 0 : 0 +>x : number +>0 : 0 + + y; // <- bad +>y : 0 + + z; // <- bad +>z : number + + ok; +>ok : number + } + let f1 = function () { +>f1 : () => void +>function () { x = 0; // <- bad y; // <- bad z; // <- bad ok; } : () => void + + x = 0; // <- bad +>x = 0 : 0 +>x : number +>0 : 0 + + y; // <- bad +>y : 0 + + z; // <- bad +>z : number + + ok; +>ok : number + } + break; + + case 2: +>2 : 2 + + case 3: +>3 : 3 + + x; // <- bad +>x : number + + y; // <- bad +>y : 0 + + z; // <- bad +>z : number + + ok; +>ok : number + + if (true) { +>true : true + + x; // <- bad +>x : number + + y; // <- bad +>y : 0 + + z; // <- bad +>z : number + + ok; +>ok : number + } + let f2 = function () { +>f2 : () => void +>function () { x; // <- bad y; // <- bad z; // <- bad ok; } : () => void + + x; // <- bad +>x : number + + y; // <- bad +>y : 0 + + z; // <- bad +>z : number + + ok; +>ok : number + } + +} + diff --git a/tests/baselines/reference/unusedSwitchStatement.errors.txt b/tests/baselines/reference/unusedSwitchStatement.errors.txt index fbb6212ca3eed..af8a5c9cb9021 100644 --- a/tests/baselines/reference/unusedSwitchStatement.errors.txt +++ b/tests/baselines/reference/unusedSwitchStatement.errors.txt @@ -5,9 +5,10 @@ tests/cases/compiler/unusedSwitchStatement.ts(9,13): error TS6133: 'z' is declar tests/cases/compiler/unusedSwitchStatement.ts(14,10): error TS2678: Type '0' is not comparable to type '2'. tests/cases/compiler/unusedSwitchStatement.ts(15,13): error TS6133: 'x' is declared but its value is never read. tests/cases/compiler/unusedSwitchStatement.ts(16,10): error TS2678: Type '1' is not comparable to type '2'. +tests/cases/compiler/unusedSwitchStatement.ts(17,9): error TS2837: Variable 'x' is declared in a prior case block. -==== tests/cases/compiler/unusedSwitchStatement.ts (7 errors) ==== +==== tests/cases/compiler/unusedSwitchStatement.ts (8 errors) ==== switch (1) { case 0: ~ @@ -39,4 +40,6 @@ tests/cases/compiler/unusedSwitchStatement.ts(16,10): error TS2678: Type '1' is ~ !!! error TS2678: Type '1' is not comparable to type '2'. x++; + ~ +!!! error TS2837: Variable 'x' is declared in a prior case block. } \ No newline at end of file diff --git a/tests/cases/compiler/switchCaseTdz.ts b/tests/cases/compiler/switchCaseTdz.ts new file mode 100644 index 0000000000000..9b7e68931e80b --- /dev/null +++ b/tests/cases/compiler/switchCaseTdz.ts @@ -0,0 +1,56 @@ +switch (1 + 1) { + case -1: + x; + y; + z; + + case 0: + var ok = 0; + let x = 0; + const y = 0; + const [z] = [0]; + + ok; + x; + y; + z; + + case 1: + x = 0; // <- bad + y; // <- bad + z; // <- bad + ok; + if (true) { + x = 0; // <- bad + y; // <- bad + z; // <- bad + ok; + } + let f1 = function () { + x = 0; // <- bad + y; // <- bad + z; // <- bad + ok; + } + break; + + case 2: + case 3: + x; // <- bad + y; // <- bad + z; // <- bad + ok; + if (true) { + x; // <- bad + y; // <- bad + z; // <- bad + ok; + } + let f2 = function () { + x; // <- bad + y; // <- bad + z; // <- bad + ok; + } + +} From 309dfa3e00746bca6223a5692cc7f10bbd9129a0 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 15 Dec 2021 11:14:17 -0800 Subject: [PATCH 2/5] Fix up an illegal let --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 91b6d36eb4cf1..37c7d2b5bd4bf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26124,6 +26124,7 @@ namespace ts { // Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand. function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined { const kind = getAssignmentDeclarationKind(binaryExpression); + let valueDeclaration; switch (kind) { case AssignmentDeclarationKind.None: case AssignmentDeclarationKind.ThisProperty: @@ -26178,7 +26179,7 @@ namespace ts { case AssignmentDeclarationKind.ExportsProperty: case AssignmentDeclarationKind.Prototype: case AssignmentDeclarationKind.PrototypeProperty: - let valueDeclaration = binaryExpression.left.symbol?.valueDeclaration; + valueDeclaration = binaryExpression.left.symbol?.valueDeclaration; // falls through case AssignmentDeclarationKind.ModuleExports: valueDeclaration ||= binaryExpression.symbol?.valueDeclaration; From 40ac03ce942819be7bda2af3ef56bf3d6349b655 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 15 Dec 2021 11:14:33 -0800 Subject: [PATCH 3/5] Add nested switch block testcase --- .../reference/switchCaseTdz.errors.txt | 16 ++++++++++-- tests/baselines/reference/switchCaseTdz.js | 20 ++++++++++++++- .../baselines/reference/switchCaseTdz.symbols | 15 +++++++++++ tests/baselines/reference/switchCaseTdz.types | 25 +++++++++++++++++++ tests/cases/compiler/switchCaseTdz.ts | 10 ++++++++ 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/switchCaseTdz.errors.txt b/tests/baselines/reference/switchCaseTdz.errors.txt index 6658fa3e7c6ac..d8fcb01db97e5 100644 --- a/tests/baselines/reference/switchCaseTdz.errors.txt +++ b/tests/baselines/reference/switchCaseTdz.errors.txt @@ -19,9 +19,10 @@ tests/cases/compiler/switchCaseTdz.ts(46,13): error TS2837: Variable 'z' is decl tests/cases/compiler/switchCaseTdz.ts(50,13): error TS2837: Variable 'x' is declared in a prior case block. tests/cases/compiler/switchCaseTdz.ts(51,13): error TS2837: Variable 'y' is declared in a prior case block. tests/cases/compiler/switchCaseTdz.ts(52,13): error TS2837: Variable 'z' is declared in a prior case block. +tests/cases/compiler/switchCaseTdz.ts(64,17): error TS2837: Variable 'x' is declared in a prior case block. -==== tests/cases/compiler/switchCaseTdz.ts (21 errors) ==== +==== tests/cases/compiler/switchCaseTdz.ts (22 errors) ==== switch (1 + 1) { case -1: x; @@ -123,4 +124,15 @@ tests/cases/compiler/switchCaseTdz.ts(52,13): error TS2837: Variable 'z' is decl } } - \ No newline at end of file + + switch(2 + 2) { + case 0: + let x = 1; + switch(x + x) { + case 2: + // Legal + x; + ~ +!!! error TS2837: Variable 'x' is declared in a prior case block. + } + } \ No newline at end of file diff --git a/tests/baselines/reference/switchCaseTdz.js b/tests/baselines/reference/switchCaseTdz.js index c9b0c0f01a066..6a08fd15b28e5 100644 --- a/tests/baselines/reference/switchCaseTdz.js +++ b/tests/baselines/reference/switchCaseTdz.js @@ -55,7 +55,16 @@ switch (1 + 1) { } } - + +switch(2 + 2) { + case 0: + let x = 1; + switch(x + x) { + case 2: + // Legal + x; + } +} //// [switchCaseTdz.js] switch (1 + 1) { @@ -109,3 +118,12 @@ switch (1 + 1) { ok; }; } +switch (2 + 2) { + case 0: + var x = 1; + switch (x + x) { + case 2: + // Legal + x; + } +} diff --git a/tests/baselines/reference/switchCaseTdz.symbols b/tests/baselines/reference/switchCaseTdz.symbols index 909baa8159087..54e771d8b587d 100644 --- a/tests/baselines/reference/switchCaseTdz.symbols +++ b/tests/baselines/reference/switchCaseTdz.symbols @@ -123,3 +123,18 @@ switch (1 + 1) { } +switch(2 + 2) { + case 0: + let x = 1; +>x : Symbol(x, Decl(switchCaseTdz.ts, 59, 11)) + + switch(x + x) { +>x : Symbol(x, Decl(switchCaseTdz.ts, 59, 11)) +>x : Symbol(x, Decl(switchCaseTdz.ts, 59, 11)) + + case 2: + // Legal + x; +>x : Symbol(x, Decl(switchCaseTdz.ts, 59, 11)) + } +} diff --git a/tests/baselines/reference/switchCaseTdz.types b/tests/baselines/reference/switchCaseTdz.types index 94a50de14aabf..8911919d4305f 100644 --- a/tests/baselines/reference/switchCaseTdz.types +++ b/tests/baselines/reference/switchCaseTdz.types @@ -155,3 +155,28 @@ switch (1 + 1) { } +switch(2 + 2) { +>2 + 2 : number +>2 : 2 +>2 : 2 + + case 0: +>0 : 0 + + let x = 1; +>x : number +>1 : 1 + + switch(x + x) { +>x + x : number +>x : number +>x : number + + case 2: +>2 : 2 + + // Legal + x; +>x : number + } +} diff --git a/tests/cases/compiler/switchCaseTdz.ts b/tests/cases/compiler/switchCaseTdz.ts index 9b7e68931e80b..726b738f01f8e 100644 --- a/tests/cases/compiler/switchCaseTdz.ts +++ b/tests/cases/compiler/switchCaseTdz.ts @@ -54,3 +54,13 @@ switch (1 + 1) { } } + +switch(2 + 2) { + case 0: + let x = 1; + switch(x + x) { + case 2: + // Legal + x; + } +} \ No newline at end of file From 40faacad1dd57180ab35b3607949932561612f16 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 15 Dec 2021 11:22:51 -0800 Subject: [PATCH 4/5] Correctly handle nested switches --- src/compiler/checker.ts | 11 ++++++++--- tests/baselines/reference/switchCaseTdz.errors.txt | 5 +---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 37c7d2b5bd4bf..929892c2543e8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25258,9 +25258,14 @@ namespace ts { // if (true) x; // <- bad (note: need to walk to parent) // } if (container.kind === SyntaxKind.CaseBlock) { - const usageCaseBlock = getAncestor(node, SyntaxKind.CaseClause); - if (usageCaseBlock) { - if (usageCaseBlock.pos > symbol.valueDeclaration.pos) { + let usageCaseClause = getAncestor(node, SyntaxKind.CaseClause); + if (usageCaseClause) { + // Walk up until we're at the same level as the declaring block + while (usageCaseClause.parent !== container) { + usageCaseClause = usageCaseClause!.parent; + } + + if (usageCaseClause.pos > symbol.valueDeclaration.pos) { error(node, Diagnostics.Variable_0_is_declared_in_a_prior_case_block, symbolToString(symbol)); } } diff --git a/tests/baselines/reference/switchCaseTdz.errors.txt b/tests/baselines/reference/switchCaseTdz.errors.txt index d8fcb01db97e5..09fd648218a12 100644 --- a/tests/baselines/reference/switchCaseTdz.errors.txt +++ b/tests/baselines/reference/switchCaseTdz.errors.txt @@ -19,10 +19,9 @@ tests/cases/compiler/switchCaseTdz.ts(46,13): error TS2837: Variable 'z' is decl tests/cases/compiler/switchCaseTdz.ts(50,13): error TS2837: Variable 'x' is declared in a prior case block. tests/cases/compiler/switchCaseTdz.ts(51,13): error TS2837: Variable 'y' is declared in a prior case block. tests/cases/compiler/switchCaseTdz.ts(52,13): error TS2837: Variable 'z' is declared in a prior case block. -tests/cases/compiler/switchCaseTdz.ts(64,17): error TS2837: Variable 'x' is declared in a prior case block. -==== tests/cases/compiler/switchCaseTdz.ts (22 errors) ==== +==== tests/cases/compiler/switchCaseTdz.ts (21 errors) ==== switch (1 + 1) { case -1: x; @@ -132,7 +131,5 @@ tests/cases/compiler/switchCaseTdz.ts(64,17): error TS2837: Variable 'x' is decl case 2: // Legal x; - ~ -!!! error TS2837: Variable 'x' is declared in a prior case block. } } \ No newline at end of file From e223f1b61dcc6b60a805e507bac791f0d3cf5bdb Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 15 Dec 2021 11:37:18 -0800 Subject: [PATCH 5/5] Lint --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 929892c2543e8..462da906ba315 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25262,7 +25262,7 @@ namespace ts { if (usageCaseClause) { // Walk up until we're at the same level as the declaring block while (usageCaseClause.parent !== container) { - usageCaseClause = usageCaseClause!.parent; + usageCaseClause = usageCaseClause.parent; } if (usageCaseClause.pos > symbol.valueDeclaration.pos) {