From a4866b6234758afcdb6ef066b999ee375823af17 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:42:21 -0800 Subject: [PATCH 01/13] tests --- .../reference/reachabilityChecks10.errors.txt | 10 + .../reference/reachabilityChecks10.symbols | 16 ++ .../reference/reachabilityChecks10.types | 35 ++++ .../reference/reachabilityChecks11.errors.txt | 104 ++++++++++ .../reference/reachabilityChecks11.js | 156 +++++++++++++++ .../reference/reachabilityChecks11.symbols | 124 ++++++++++++ .../reference/reachabilityChecks11.types | 180 ++++++++++++++++++ .../reference/reachabilityChecks9.errors.txt | 35 ++++ .../reference/reachabilityChecks9.symbols | 65 +++++++ .../reference/reachabilityChecks9.types | 132 +++++++++++++ tests/cases/compiler/reachabilityChecks10.ts | 7 + tests/cases/compiler/reachabilityChecks11.ts | 76 ++++++++ tests/cases/compiler/reachabilityChecks9.ts | 29 +++ 13 files changed, 969 insertions(+) create mode 100644 tests/baselines/reference/reachabilityChecks10.errors.txt create mode 100644 tests/baselines/reference/reachabilityChecks10.symbols create mode 100644 tests/baselines/reference/reachabilityChecks10.types create mode 100644 tests/baselines/reference/reachabilityChecks11.errors.txt create mode 100644 tests/baselines/reference/reachabilityChecks11.js create mode 100644 tests/baselines/reference/reachabilityChecks11.symbols create mode 100644 tests/baselines/reference/reachabilityChecks11.types create mode 100644 tests/baselines/reference/reachabilityChecks9.errors.txt create mode 100644 tests/baselines/reference/reachabilityChecks9.symbols create mode 100644 tests/baselines/reference/reachabilityChecks9.types create mode 100644 tests/cases/compiler/reachabilityChecks10.ts create mode 100644 tests/cases/compiler/reachabilityChecks11.ts create mode 100644 tests/cases/compiler/reachabilityChecks9.ts diff --git a/tests/baselines/reference/reachabilityChecks10.errors.txt b/tests/baselines/reference/reachabilityChecks10.errors.txt new file mode 100644 index 0000000000000..0b7de2d4adb89 --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks10.errors.txt @@ -0,0 +1,10 @@ +reachabilityChecks10.ts(2,1): error TS7027: Unreachable code detected. + + +==== reachabilityChecks10.ts (1 errors) ==== + throw new Error("") + console.log("1") + ~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + console.log("2") + \ No newline at end of file diff --git a/tests/baselines/reference/reachabilityChecks10.symbols b/tests/baselines/reference/reachabilityChecks10.symbols new file mode 100644 index 0000000000000..6ecc554153996 --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks10.symbols @@ -0,0 +1,16 @@ +//// [tests/cases/compiler/reachabilityChecks10.ts] //// + +=== reachabilityChecks10.ts === +throw new Error("") +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +console.log("1") +>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, --, --)) + +console.log("2") +>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, --, --)) + diff --git a/tests/baselines/reference/reachabilityChecks10.types b/tests/baselines/reference/reachabilityChecks10.types new file mode 100644 index 0000000000000..bfe7b1bfefe8d --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks10.types @@ -0,0 +1,35 @@ +//// [tests/cases/compiler/reachabilityChecks10.ts] //// + +=== reachabilityChecks10.ts === +throw new Error("") +>new Error("") : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +>"" : "" +> : ^^ + +console.log("1") +>console.log("1") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"1" : "1" +> : ^^^ + +console.log("2") +>console.log("2") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"2" : "2" +> : ^^^ + diff --git a/tests/baselines/reference/reachabilityChecks11.errors.txt b/tests/baselines/reference/reachabilityChecks11.errors.txt new file mode 100644 index 0000000000000..b4497ee46432a --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks11.errors.txt @@ -0,0 +1,104 @@ +reachabilityChecks11.ts(6,5): error TS7027: Unreachable code detected. +reachabilityChecks11.ts(18,5): error TS7027: Unreachable code detected. +reachabilityChecks11.ts(30,5): error TS7027: Unreachable code detected. +reachabilityChecks11.ts(47,5): error TS7027: Unreachable code detected. +reachabilityChecks11.ts(60,5): error TS7027: Unreachable code detected. +reachabilityChecks11.ts(69,5): error TS7027: Unreachable code detected. + + +==== reachabilityChecks11.ts (6 errors) ==== + // while (true); + var x = 1; + + module A { + while (true); + let x; + ~~~~~~ +!!! error TS7027: Unreachable code detected. + } + + module A1 { + do {} while(true); + module A { + interface F {} + } + } + + module A2 { + while (true); + module A { + ~~~~~~~~~~ + var x = 1; + ~~~~~~~~~~~~~~~~~~ + } + ~~~~~ +!!! error TS7027: Unreachable code detected. + } + + module A3 { + while (true); + type T = string; + } + + module A4 { + while (true); + module A { + ~~~~~~~~~~ + const enum E { X } + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + } + ~~~~~ +!!! error TS7027: Unreachable code detected. + } + + function f1(x) { + if (x) { + return; + } + else { + throw new Error("123"); + } + var x; + } + + function f2() { + return; + class A { + ~~~~~~~~~ + } + ~~~~~ +!!! error TS7027: Unreachable code detected. + } + + module B { + for (; ;); + module C { + } + } + + function f3() { + do { + } while (true); + enum E { + ~~~~~~~~ + X = 1 + ~~~~~~~~~~~~~ + } + ~~~~~ +!!! error TS7027: Unreachable code detected. + } + + function f4() { + if (true) { + throw new Error(); + } + const enum E { + ~~~~~~~~~~~~~~ + X = 1 + ~~~~~~~~~~~~~ + } + ~~~~~ +!!! error TS7027: Unreachable code detected. + } + + \ No newline at end of file diff --git a/tests/baselines/reference/reachabilityChecks11.js b/tests/baselines/reference/reachabilityChecks11.js new file mode 100644 index 0000000000000..d115e58e816cb --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks11.js @@ -0,0 +1,156 @@ +//// [tests/cases/compiler/reachabilityChecks11.ts] //// + +//// [reachabilityChecks11.ts] +// while (true); +var x = 1; + +module A { + while (true); + let x; +} + +module A1 { + do {} while(true); + module A { + interface F {} + } +} + +module A2 { + while (true); + module A { + var x = 1; + } +} + +module A3 { + while (true); + type T = string; +} + +module A4 { + while (true); + module A { + const enum E { X } + } +} + +function f1(x) { + if (x) { + return; + } + else { + throw new Error("123"); + } + var x; +} + +function f2() { + return; + class A { + } +} + +module B { + for (; ;); + module C { + } +} + +function f3() { + do { + } while (true); + enum E { + X = 1 + } +} + +function f4() { + if (true) { + throw new Error(); + } + const enum E { + X = 1 + } +} + + + +//// [reachabilityChecks11.js] +// while (true); +var x = 1; +var A; +(function (A) { + while (true) + ; + var x; +})(A || (A = {})); +var A1; +(function (A1) { + do { } while (true); +})(A1 || (A1 = {})); +var A2; +(function (A2) { + while (true) + ; + var A; + (function (A) { + var x = 1; + })(A || (A = {})); +})(A2 || (A2 = {})); +var A3; +(function (A3) { + while (true) + ; +})(A3 || (A3 = {})); +var A4; +(function (A4) { + while (true) + ; + var A; + (function (A) { + var E; + (function (E) { + E[E["X"] = 0] = "X"; + })(E || (E = {})); + })(A || (A = {})); +})(A4 || (A4 = {})); +function f1(x) { + if (x) { + return; + } + else { + throw new Error("123"); + } + var x; +} +function f2() { + return; + var A = /** @class */ (function () { + function A() { + } + return A; + }()); +} +var B; +(function (B) { + for (;;) + ; +})(B || (B = {})); +function f3() { + do { + } while (true); + var E; + (function (E) { + E[E["X"] = 1] = "X"; + })(E || (E = {})); +} +function f4() { + if (true) { + throw new Error(); + } + var E; + (function (E) { + E[E["X"] = 1] = "X"; + })(E || (E = {})); +} diff --git a/tests/baselines/reference/reachabilityChecks11.symbols b/tests/baselines/reference/reachabilityChecks11.symbols new file mode 100644 index 0000000000000..450ccad803ff5 --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks11.symbols @@ -0,0 +1,124 @@ +//// [tests/cases/compiler/reachabilityChecks11.ts] //// + +=== reachabilityChecks11.ts === +// while (true); +var x = 1; +>x : Symbol(x, Decl(reachabilityChecks11.ts, 1, 3)) + +module A { +>A : Symbol(A, Decl(reachabilityChecks11.ts, 1, 10)) + + while (true); + let x; +>x : Symbol(x, Decl(reachabilityChecks11.ts, 5, 7)) +} + +module A1 { +>A1 : Symbol(A1, Decl(reachabilityChecks11.ts, 6, 1)) + + do {} while(true); + module A { +>A : Symbol(A, Decl(reachabilityChecks11.ts, 9, 22)) + + interface F {} +>F : Symbol(F, Decl(reachabilityChecks11.ts, 10, 14)) + } +} + +module A2 { +>A2 : Symbol(A2, Decl(reachabilityChecks11.ts, 13, 1)) + + while (true); + module A { +>A : Symbol(A, Decl(reachabilityChecks11.ts, 16, 17)) + + var x = 1; +>x : Symbol(x, Decl(reachabilityChecks11.ts, 18, 11)) + } +} + +module A3 { +>A3 : Symbol(A3, Decl(reachabilityChecks11.ts, 20, 1)) + + while (true); + type T = string; +>T : Symbol(T, Decl(reachabilityChecks11.ts, 23, 17)) +} + +module A4 { +>A4 : Symbol(A4, Decl(reachabilityChecks11.ts, 25, 1)) + + while (true); + module A { +>A : Symbol(A, Decl(reachabilityChecks11.ts, 28, 17)) + + const enum E { X } +>E : Symbol(E, Decl(reachabilityChecks11.ts, 29, 14)) +>X : Symbol(E.X, Decl(reachabilityChecks11.ts, 30, 22)) + } +} + +function f1(x) { +>f1 : Symbol(f1, Decl(reachabilityChecks11.ts, 32, 1)) +>x : Symbol(x, Decl(reachabilityChecks11.ts, 34, 12), Decl(reachabilityChecks11.ts, 41, 7)) + + if (x) { +>x : Symbol(x, Decl(reachabilityChecks11.ts, 34, 12), Decl(reachabilityChecks11.ts, 41, 7)) + + return; + } + else { + throw new Error("123"); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + var x; +>x : Symbol(x, Decl(reachabilityChecks11.ts, 34, 12), Decl(reachabilityChecks11.ts, 41, 7)) +} + +function f2() { +>f2 : Symbol(f2, Decl(reachabilityChecks11.ts, 42, 1)) + + return; + class A { +>A : Symbol(A, Decl(reachabilityChecks11.ts, 45, 11)) + } +} + +module B { +>B : Symbol(B, Decl(reachabilityChecks11.ts, 48, 1)) + + for (; ;); + module C { +>C : Symbol(C, Decl(reachabilityChecks11.ts, 51, 14)) + } +} + +function f3() { +>f3 : Symbol(f3, Decl(reachabilityChecks11.ts, 54, 1)) + + do { + } while (true); + enum E { +>E : Symbol(E, Decl(reachabilityChecks11.ts, 58, 19)) + + X = 1 +>X : Symbol(E.X, Decl(reachabilityChecks11.ts, 59, 12)) + } +} + +function f4() { +>f4 : Symbol(f4, Decl(reachabilityChecks11.ts, 62, 1)) + + if (true) { + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + const enum E { +>E : Symbol(E, Decl(reachabilityChecks11.ts, 67, 5)) + + X = 1 +>X : Symbol(E.X, Decl(reachabilityChecks11.ts, 68, 18)) + } +} + + diff --git a/tests/baselines/reference/reachabilityChecks11.types b/tests/baselines/reference/reachabilityChecks11.types new file mode 100644 index 0000000000000..49fcc60359573 --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks11.types @@ -0,0 +1,180 @@ +//// [tests/cases/compiler/reachabilityChecks11.ts] //// + +=== reachabilityChecks11.ts === +// while (true); +var x = 1; +>x : number +> : ^^^^^^ +>1 : 1 +> : ^ + +module A { +>A : typeof A +> : ^^^^^^^^ + + while (true); +>true : true +> : ^^^^ + + let x; +>x : any +> : ^^^ +} + +module A1 { +>A1 : typeof A1 +> : ^^^^^^^^^ + + do {} while(true); +>true : true +> : ^^^^ + + module A { + interface F {} + } +} + +module A2 { +>A2 : typeof A2 +> : ^^^^^^^^^ + + while (true); +>true : true +> : ^^^^ + + module A { +>A : typeof A +> : ^^^^^^^^ + + var x = 1; +>x : number +> : ^^^^^^ +>1 : 1 +> : ^ + } +} + +module A3 { +>A3 : typeof A3 +> : ^^^^^^^^^ + + while (true); +>true : true +> : ^^^^ + + type T = string; +>T : string +> : ^^^^^^ +} + +module A4 { +>A4 : typeof A4 +> : ^^^^^^^^^ + + while (true); +>true : true +> : ^^^^ + + module A { + const enum E { X } +>E : E +> : ^ +>X : E.X +> : ^^^ + } +} + +function f1(x) { +>f1 : (x: any) => void +> : ^ ^^^^^^^^^^^^^^ +>x : any +> : ^^^ + + if (x) { +>x : any +> : ^^^ + + return; + } + else { + throw new Error("123"); +>new Error("123") : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +>"123" : "123" +> : ^^^^^ + } + var x; +>x : any +> : ^^^ +} + +function f2() { +>f2 : () => void +> : ^^^^^^^^^^ + + return; + class A { +>A : A +> : ^ + } +} + +module B { +>B : typeof B +> : ^^^^^^^^ + + for (; ;); + module C { + } +} + +function f3() { +>f3 : () => void +> : ^^^^^^^^^^ + + do { + } while (true); +>true : true +> : ^^^^ + + enum E { +>E : E +> : ^ + + X = 1 +>X : E.X +> : ^^^ +>1 : 1 +> : ^ + } +} + +function f4() { +>f4 : () => void +> : ^^^^^^^^^^ + + if (true) { +>true : true +> : ^^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } + const enum E { +>E : E +> : ^ + + X = 1 +>X : E.X +> : ^^^ +>1 : 1 +> : ^ + } +} + + diff --git a/tests/baselines/reference/reachabilityChecks9.errors.txt b/tests/baselines/reference/reachabilityChecks9.errors.txt new file mode 100644 index 0000000000000..6d51345ca2e0e --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks9.errors.txt @@ -0,0 +1,35 @@ +reachabilityChecks9.ts(7,7): error TS7027: Unreachable code detected. +reachabilityChecks9.ts(20,7): error TS7027: Unreachable code detected. + + +==== reachabilityChecks9.ts (2 errors) ==== + // https://github.com/microsoft/TypeScript/issues/55562 + + function g(str: string) { + switch (str) { + case "a": + return; + console.log("1"); + ~~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + console.log("2"); + case "b": + console.log("3"); + } + } + + function h(str: string) { + switch (str) { + case "a": + console.log("1"); + default: + return; + console.log("2"); + ~~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + console.log("3"); + case "b": + console.log("4"); + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/reachabilityChecks9.symbols b/tests/baselines/reference/reachabilityChecks9.symbols new file mode 100644 index 0000000000000..d50f997b9aabc --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks9.symbols @@ -0,0 +1,65 @@ +//// [tests/cases/compiler/reachabilityChecks9.ts] //// + +=== reachabilityChecks9.ts === +// https://github.com/microsoft/TypeScript/issues/55562 + +function g(str: string) { +>g : Symbol(g, Decl(reachabilityChecks9.ts, 0, 0)) +>str : Symbol(str, Decl(reachabilityChecks9.ts, 2, 11)) + + switch (str) { +>str : Symbol(str, Decl(reachabilityChecks9.ts, 2, 11)) + + case "a": + return; + console.log("1"); +>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, --, --)) + + console.log("2"); +>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, --, --)) + + case "b": + console.log("3"); +>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, --, --)) + } +} + +function h(str: string) { +>h : Symbol(h, Decl(reachabilityChecks9.ts, 11, 1)) +>str : Symbol(str, Decl(reachabilityChecks9.ts, 13, 11)) + + switch (str) { +>str : Symbol(str, Decl(reachabilityChecks9.ts, 13, 11)) + + case "a": + console.log("1"); +>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, --, --)) + + default: + return; + console.log("2"); +>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, --, --)) + + console.log("3"); +>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, --, --)) + + case "b": + console.log("4"); +>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, --, --)) + } +} + diff --git a/tests/baselines/reference/reachabilityChecks9.types b/tests/baselines/reference/reachabilityChecks9.types new file mode 100644 index 0000000000000..d428078568a98 --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks9.types @@ -0,0 +1,132 @@ +//// [tests/cases/compiler/reachabilityChecks9.ts] //// + +=== reachabilityChecks9.ts === +// https://github.com/microsoft/TypeScript/issues/55562 + +function g(str: string) { +>g : (str: string) => void +> : ^ ^^ ^^^^^^^^^ +>str : string +> : ^^^^^^ + + switch (str) { +>str : string +> : ^^^^^^ + + case "a": +>"a" : "a" +> : ^^^ + + return; + console.log("1"); +>console.log("1") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"1" : "1" +> : ^^^ + + console.log("2"); +>console.log("2") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"2" : "2" +> : ^^^ + + case "b": +>"b" : "b" +> : ^^^ + + console.log("3"); +>console.log("3") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"3" : "3" +> : ^^^ + } +} + +function h(str: string) { +>h : (str: string) => void +> : ^ ^^ ^^^^^^^^^ +>str : string +> : ^^^^^^ + + switch (str) { +>str : string +> : ^^^^^^ + + case "a": +>"a" : "a" +> : ^^^ + + console.log("1"); +>console.log("1") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"1" : "1" +> : ^^^ + + default: + return; + console.log("2"); +>console.log("2") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"2" : "2" +> : ^^^ + + console.log("3"); +>console.log("3") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"3" : "3" +> : ^^^ + + case "b": +>"b" : "b" +> : ^^^ + + console.log("4"); +>console.log("4") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"4" : "4" +> : ^^^ + } +} + diff --git a/tests/cases/compiler/reachabilityChecks10.ts b/tests/cases/compiler/reachabilityChecks10.ts new file mode 100644 index 0000000000000..e4f17b935ce0f --- /dev/null +++ b/tests/cases/compiler/reachabilityChecks10.ts @@ -0,0 +1,7 @@ +// @strict: true +// @noEmit: true +// @allowUnreachableCode: false + +throw new Error("") +console.log("1") +console.log("2") diff --git a/tests/cases/compiler/reachabilityChecks11.ts b/tests/cases/compiler/reachabilityChecks11.ts new file mode 100644 index 0000000000000..81dbcf3c7ad81 --- /dev/null +++ b/tests/cases/compiler/reachabilityChecks11.ts @@ -0,0 +1,76 @@ +// @allowUnreachableCode: false +// @preserveConstEnums: true + +// while (true); +var x = 1; + +module A { + while (true); + let x; +} + +module A1 { + do {} while(true); + module A { + interface F {} + } +} + +module A2 { + while (true); + module A { + var x = 1; + } +} + +module A3 { + while (true); + type T = string; +} + +module A4 { + while (true); + module A { + const enum E { X } + } +} + +function f1(x) { + if (x) { + return; + } + else { + throw new Error("123"); + } + var x; +} + +function f2() { + return; + class A { + } +} + +module B { + for (; ;); + module C { + } +} + +function f3() { + do { + } while (true); + enum E { + X = 1 + } +} + +function f4() { + if (true) { + throw new Error(); + } + const enum E { + X = 1 + } +} + diff --git a/tests/cases/compiler/reachabilityChecks9.ts b/tests/cases/compiler/reachabilityChecks9.ts new file mode 100644 index 0000000000000..601cdb65d4abd --- /dev/null +++ b/tests/cases/compiler/reachabilityChecks9.ts @@ -0,0 +1,29 @@ +// @strict: true +// @noEmit: true +// @allowUnreachableCode: false + +// https://github.com/microsoft/TypeScript/issues/55562 + +function g(str: string) { + switch (str) { + case "a": + return; + console.log("1"); + console.log("2"); + case "b": + console.log("3"); + } +} + +function h(str: string) { + switch (str) { + case "a": + console.log("1"); + default: + return; + console.log("2"); + console.log("3"); + case "b": + console.log("4"); + } +} From 6c226f260d858278a0b5aab8919dc820ef0c9f57 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 12 Nov 2025 21:13:36 -0800 Subject: [PATCH 02/13] Port it --- src/compiler/binder.ts | 141 ++---------------- src/compiler/checker.ts | 78 +++++++++- src/compiler/types.ts | 1 + .../neverReturningFunctions1.errors.txt | 8 +- .../reference/reachabilityChecks1.errors.txt | 47 ++++-- .../reference/reachabilityChecks10.errors.txt | 3 +- .../reference/reachabilityChecks9.errors.txt | 6 +- ...xUnreachableCode_noSuggestionIfDisabled.ts | 10 +- ...deFixUnusedLabel_noSuggestionIfDisabled.ts | 10 +- 9 files changed, 151 insertions(+), 153 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 27ec079f614ab..23ed4ea6517a1 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -46,7 +46,6 @@ import { DeleteExpression, DestructuringAssignment, DiagnosticArguments, - DiagnosticCategory, DiagnosticMessage, DiagnosticRelatedInformation, Diagnostics, @@ -87,7 +86,6 @@ import { getAssignmentDeclarationKind, getAssignmentDeclarationPropertyAccessKind, getCombinedModifierFlags, - getCombinedNodeFlags, getContainingClass, getEffectiveContainerForJSDocTemplateTag, getElementOrPropertyAccessName, @@ -106,7 +104,6 @@ import { getNameOfDeclaration, getNameOrArgument, getNodeId, - getRangesWhere, getRightMostAssignedExpression, getSourceFileOfNode, getSourceTextOfNodeFromSourceFile, @@ -115,10 +112,8 @@ import { getSymbolNameForPrivateIdentifier, getTextOfIdentifierOrLiteral, getThisContainer, - getTokenPosOfNode, HasContainerFlags, hasDynamicName, - HasFlowNode, hasJSDocNodes, HasLocals, hasSyntacticModifier, @@ -164,7 +159,6 @@ import { isExternalModule, isExternalOrCommonJsModule, isForInOrOfStatement, - isFunctionDeclaration, isFunctionLike, isFunctionLikeDeclaration, isFunctionLikeOrClassStaticBlockDeclaration, @@ -217,8 +211,6 @@ import { isSignedNumericLiteral, isSourceFile, isSpecialPropertyDeclaration, - isStatement, - isStatementButNotDeclaration, isStatic, isString, isStringLiteralLike, @@ -286,10 +278,8 @@ import { setParentRecursive, setValueDeclaration, ShorthandPropertyAssignment, - shouldPreserveConstEnums, SignatureDeclaration, skipParentheses, - sliceAfter, some, SourceFile, SpreadElement, @@ -302,7 +292,6 @@ import { symbolName, SymbolTable, SyntaxKind, - TextRange, ThisExpression, ThrowStatement, tokenToString, @@ -315,8 +304,6 @@ import { TypeOfExpression, TypeParameterDeclaration, unescapeLeadingUnderscores, - unreachableCodeIsError, - unusedLabelIsError, VariableDeclaration, WhileStatement, WithStatement, @@ -565,7 +552,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { var classifiableNames: Set<__String>; var unreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined); - var reportedUnreachableFlow = createFlowNode(FlowFlags.Unreachable, /*node*/ undefined, /*antecedent*/ undefined); var bindBinaryExpressionFlow = createBindBinaryExpressionFlow(); /* eslint-enable no-var */ @@ -592,7 +578,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { // Attach debugging information if necessary Debug.attachFlowNodeDebugInfo(unreachableFlow); - Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); if (!file.locals) { tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true); @@ -1104,18 +1089,23 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { // Most nodes aren't valid in an assignment pattern, so we clear the value here // and set it before we descend into nodes that could actually be part of an assignment pattern. inAssignmentPattern = false; - if (checkUnreachable(node)) { - if (canHaveFlowNode(node) && node.flowNode) { - node.flowNode = undefined; + + const isPotentiallyExecutableStatement = (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement) || + node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.EnumDeclaration || node.kind === SyntaxKind.ModuleDeclaration; + + if (isPotentiallyExecutableStatement && canHaveFlowNode(node)) { + node.flowNode = currentFlow; + } + + if (currentFlow === unreachableFlow) { + if (isPotentiallyExecutableStatement) { + (node as Mutable).flags |= NodeFlags.Unreachable; } bindEachChild(node); bindJSDoc(node); inAssignmentPattern = saveInAssignmentPattern; return; } - if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && (!options.allowUnreachableCode || node.kind === SyntaxKind.ReturnStatement)) { - (node as HasFlowNode).flowNode = currentFlow; - } switch (node.kind) { case SyntaxKind.WhileStatement: bindWhileStatement(node as WhileStatement); @@ -1793,8 +1783,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { }; bind(node.label); bind(node.statement); - if (!activeLabelList.referenced && !options.allowUnusedLabels) { - errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); + if (!activeLabelList.referenced) { + (node.label as Mutable).flags |= NodeFlags.Unreachable; } activeLabelList = activeLabelList.next; addAntecedent(postStatementLabel, currentFlow); @@ -2743,24 +2733,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, ...args)); } - function errorOrSuggestionOnNode(isError: boolean, node: Node, message: DiagnosticMessage): void { - errorOrSuggestionOnRange(isError, node, node, message); - } - - function errorOrSuggestionOnRange(isError: boolean, startNode: Node, endNode: Node, message: DiagnosticMessage): void { - addErrorOrSuggestionDiagnostic(isError, { pos: getTokenPosOfNode(startNode, file), end: endNode.end }, message); - } - - function addErrorOrSuggestionDiagnostic(isError: boolean, range: TextRange, message: DiagnosticMessage): void { - const diag = createFileDiagnostic(file, range.pos, range.end - range.pos, message); - if (isError) { - file.bindDiagnostics.push(diag); - } - else { - file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion }); - } - } - function bind(node: Node | undefined): void { if (!node) { return; @@ -3793,93 +3765,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } } - - // reachability checks - - function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { - const instanceState = getModuleInstanceState(node); - return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && shouldPreserveConstEnums(options)); - } - - function checkUnreachable(node: Node): boolean { - if (!(currentFlow.flags & FlowFlags.Unreachable)) { - return false; - } - if (currentFlow === unreachableFlow) { - const reportError = - // report error on all statements except empty ones - (isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) || - // report error on class declarations - node.kind === SyntaxKind.ClassDeclaration || - // report errors on enums with preserved emit - isEnumDeclarationWithPreservedEmit(node, options) || - // report error on instantiated modules - (node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(node as ModuleDeclaration)); - - if (reportError) { - currentFlow = reportedUnreachableFlow; - - if (!options.allowUnreachableCode) { - // unreachable code is reported if - // - user has explicitly asked about it AND - // - statement is in not ambient context (statements in ambient context is already an error - // so we should not report extras) AND - // - node is not variable statement OR - // - node is block scoped variable statement OR - // - node is not block scoped variable statement and at least one variable declaration has initializer - // Rationale: we don't want to report errors on non-initialized var's since they are hoisted - // On the other side we do want to report errors on non-initialized 'lets' because of TDZ - const isError = unreachableCodeIsError(options) && - !(node.flags & NodeFlags.Ambient) && - ( - !isVariableStatement(node) || - !!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) || - node.declarationList.declarations.some(d => !!d.initializer) - ); - - eachUnreachableRange(node, options, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected)); - } - } - } - return true; - } -} - -function isEnumDeclarationWithPreservedEmit(node: Node, options: CompilerOptions): boolean { - return node.kind === SyntaxKind.EnumDeclaration && (!isEnumConst(node as EnumDeclaration) || shouldPreserveConstEnums(options)); -} - -function eachUnreachableRange(node: Node, options: CompilerOptions, cb: (start: Node, last: Node) => void): void { - if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) { - const { statements } = node.parent; - const slice = sliceAfter(statements, node); - getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1])); - } - else { - cb(node, node); - } - - // As opposed to a pure declaration like an `interface` - function isExecutableStatement(s: Statement): boolean { - // Don't remove statements that can validly be used before they appear. - return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && - // `var x;` may declare a variable used above - !(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.BlockScoped)) && s.declarationList.declarations.some(d => !d.initializer)); - } - - function isPurelyTypeDeclaration(s: Statement): boolean { - switch (s.kind) { - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return true; - case SyntaxKind.ModuleDeclaration: - return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated; - case SyntaxKind.EnumDeclaration: - return !isEnumDeclarationWithPreservedEmit(s, options); - default: - return false; - } - } } /** @internal */ diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fa2a56e2fdfa6..78ded541d9e69 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1118,6 +1118,8 @@ import { UnionType, UnionTypeNode, UniqueESSymbolType, + unreachableCodeIsError, + unusedLabelIsError, usingSingleLineStringWriter, VariableDeclaration, VariableDeclarationList, @@ -1512,6 +1514,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var currentNode: Node | undefined; var varianceTypeParameter: TypeParameter | undefined; var isInferencePartiallyBlocked = false; + var withinUnreachableCode = false; + var reportedUnreachableNodes: Set | undefined; var emptySymbols = createSymbolTable(); var arrayVariances = [VarianceFlags.Covariant]; @@ -46603,6 +46607,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); } + if (node.label.flags & NodeFlags.Unreachable) { + errorOrSuggestion(unusedLabelIsError(compilerOptions), node.label, Diagnostics.Unused_label); + } + // ensure that label is unique checkSourceElement(node.statement); } @@ -48967,10 +48975,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkSourceElement(node: Node | undefined): void { if (node) { const saveCurrentNode = currentNode; + const saveWithinUnreachableCode = withinUnreachableCode; currentNode = node; instantiationCount = 0; checkSourceElementWorker(node); currentNode = saveCurrentNode; + withinUnreachableCode = saveWithinUnreachableCode; } } @@ -49003,8 +49013,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { cancellationToken.throwIfCancellationRequested(); } } - if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && canHaveFlowNode(node) && node.flowNode && !isReachableFlowNode(node.flowNode)) { - errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + + if (!withinUnreachableCode) { + if (checkSourceElementUnreachable(node)) { + withinUnreachableCode = true; + } } // If editing this, keep `isSourceElement` in utilities up to date. @@ -49185,6 +49198,67 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function checkSourceElementUnreachable(node: Node): boolean { + reportedUnreachableNodes ||= new Set(); + + if (reportedUnreachableNodes.has(node)) { + return true; + } + + if (!isSourceElementUnreachable(node)) { + return false; + } + + reportedUnreachableNodes.add(node); + + const isError = unreachableCodeIsError(compilerOptions); + const sourceFile = getSourceFileOfNode(node); + + const start = skipTrivia(sourceFile.text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + let end = node.end; + + const parent = node.parent; + if (parent && (isBlock(parent) || isModuleBlock(parent) || isSourceFile(parent) || parent.kind === SyntaxKind.CaseClause || parent.kind === SyntaxKind.DefaultClause)) { + const statements = (parent as Block | ModuleBlock | SourceFile | CaseClause | DefaultClause).statements; + const offset = statements.indexOf(node as Statement); + if (offset >= 0) { + for (let i = offset + 1; i < statements.length; i++) { + const nextNode = statements[i]; + if (!isSourceElementUnreachable(nextNode)) { + break; + } + end = nextNode.end; + reportedUnreachableNodes.add(nextNode); + } + } + } + + addErrorOrSuggestion(isError, createFileDiagnostic(sourceFile, start, end - start, Diagnostics.Unreachable_code_detected)); + + return true; + } + + function isSourceElementUnreachable(node: Node): boolean { + if (node.flags & NodeFlags.Unreachable) { + switch (node.kind) { + case SyntaxKind.EnumDeclaration: + return !isEnumConst(node as EnumDeclaration) || shouldPreserveConstEnums(compilerOptions); + case SyntaxKind.ModuleDeclaration: + return isInstantiatedModule(node as ModuleDeclaration, shouldPreserveConstEnums(compilerOptions)); + case SyntaxKind.VariableStatement: + return !!(getCombinedNodeFlags((node as VariableStatement).declarationList) & NodeFlags.BlockScoped) || (node as VariableStatement).declarationList.declarations.some(d => d.initializer); + default: + return true; + } + } + else if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement) { + if (canHaveFlowNode(node) && node.flowNode) { + return !isReachableFlowNode(node.flowNode); + } + } + return false; + } + function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) { if (isArray(node)) { forEach(node, tag => { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 626bcecf9d34a..617ec4d8bda75 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -823,6 +823,7 @@ export const enum NodeFlags { JsonFile = 1 << 27, // If node was parsed in a Json /** @internal */ TypeCached = 1 << 28, // If a type was cached for node at any point /** @internal */ Deprecated = 1 << 29, // If has '@deprecated' JSDoc tag + /** @internal */ Unreachable = 1 << 30, // If node is unreachable according to the binder BlockScoped = Let | Const | Using, Constant = Const | Using, diff --git a/tests/baselines/reference/neverReturningFunctions1.errors.txt b/tests/baselines/reference/neverReturningFunctions1.errors.txt index 8a7a8837147b9..a15d15e17dbba 100644 --- a/tests/baselines/reference/neverReturningFunctions1.errors.txt +++ b/tests/baselines/reference/neverReturningFunctions1.errors.txt @@ -13,7 +13,6 @@ neverReturningFunctions1.ts(101,13): error TS7027: Unreachable code detected. neverReturningFunctions1.ts(103,9): error TS7027: Unreachable code detected. neverReturningFunctions1.ts(105,5): error TS7027: Unreachable code detected. neverReturningFunctions1.ts(111,9): error TS7027: Unreachable code detected. -neverReturningFunctions1.ts(112,9): error TS7027: Unreachable code detected. neverReturningFunctions1.ts(122,9): error TS7027: Unreachable code detected. neverReturningFunctions1.ts(127,9): error TS7027: Unreachable code detected. neverReturningFunctions1.ts(129,5): error TS7027: Unreachable code detected. @@ -23,7 +22,7 @@ neverReturningFunctions1.ts(148,9): error TS7027: Unreachable code detected. neverReturningFunctions1.ts(153,5): error TS7027: Unreachable code detected. -==== neverReturningFunctions1.ts (23 errors) ==== +==== neverReturningFunctions1.ts (22 errors) ==== function fail(message?: string): never { throw new Error(message); } @@ -163,10 +162,9 @@ neverReturningFunctions1.ts(153,5): error TS7027: Unreachable code detected. if (typeof x.a === "string") { fail(); x; // Unreachable - ~~ -!!! error TS7027: Unreachable code detected. + ~~~~~~~~~~~~~~~~~~~~ x.a; // Unreachable - ~~~~ + ~~~~~~~~~~~~ !!! error TS7027: Unreachable code detected. } x; // { a: string | number } diff --git a/tests/baselines/reference/reachabilityChecks1.errors.txt b/tests/baselines/reference/reachabilityChecks1.errors.txt index 8220dd8a921b3..ff7ff2d4a07f5 100644 --- a/tests/baselines/reference/reachabilityChecks1.errors.txt +++ b/tests/baselines/reference/reachabilityChecks1.errors.txt @@ -1,58 +1,77 @@ reachabilityChecks1.ts(2,1): error TS7027: Unreachable code detected. -reachabilityChecks1.ts(6,5): error TS7027: Unreachable code detected. -reachabilityChecks1.ts(18,5): error TS7027: Unreachable code detected. -reachabilityChecks1.ts(30,5): error TS7027: Unreachable code detected. reachabilityChecks1.ts(47,5): error TS7027: Unreachable code detected. +reachabilityChecks1.ts(51,1): error TS7027: Unreachable code detected. reachabilityChecks1.ts(60,5): error TS7027: Unreachable code detected. reachabilityChecks1.ts(69,5): error TS7027: Unreachable code detected. -==== reachabilityChecks1.ts (7 errors) ==== +==== reachabilityChecks1.ts (5 errors) ==== while (true); var x = 1; ~~~~~~~~~~ -!!! error TS7027: Unreachable code detected. + namespace A { + ~~~~~~~~~~~~~ while (true); + ~~~~~~~~~~~~~~~~~ let x; - ~~~~~~ -!!! error TS7027: Unreachable code detected. + ~~~~~~~~~~ } + ~ + namespace A1 { + ~~~~~~~~~~~~~~ do {} while(true); + ~~~~~~~~~~~~~~~~~~~~~~ namespace A { + ~~~~~~~~~~~~~~~~~ interface F {} + ~~~~~~~~~~~~~~~~~~~~~~ } + ~~~~~ } + ~ + namespace A2 { + ~~~~~~~~~~~~~~ while (true); + ~~~~~~~~~~~~~~~~~ namespace A { - ~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~ var x = 1; ~~~~~~~~~~~~~~~~~~ } ~~~~~ -!!! error TS7027: Unreachable code detected. } + ~ + namespace A3 { + ~~~~~~~~~~~~~~ while (true); + ~~~~~~~~~~~~~~~~~ type T = string; + ~~~~~~~~~~~~~~~~~~~~ } + ~ + namespace A4 { + ~~~~~~~~~~~~~~ while (true); + ~~~~~~~~~~~~~~~~~ namespace A { - ~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~ const enum E { X } ~~~~~~~~~~~~~~~~~~~~~~~~~~ } ~~~~~ -!!! error TS7027: Unreachable code detected. } + ~ +!!! error TS7027: Unreachable code detected. function f1(x) { if (x) { @@ -74,10 +93,16 @@ reachabilityChecks1.ts(69,5): error TS7027: Unreachable code detected. } namespace B { + ~~~~~~~~~~~~~ for (; ;); + ~~~~~~~~~~~~~~ namespace C { + ~~~~~~~~~~~~~~~~~ } + ~~~~~ } + ~ +!!! error TS7027: Unreachable code detected. function f3() { do { diff --git a/tests/baselines/reference/reachabilityChecks10.errors.txt b/tests/baselines/reference/reachabilityChecks10.errors.txt index 0b7de2d4adb89..2b26eae20f99e 100644 --- a/tests/baselines/reference/reachabilityChecks10.errors.txt +++ b/tests/baselines/reference/reachabilityChecks10.errors.txt @@ -5,6 +5,7 @@ reachabilityChecks10.ts(2,1): error TS7027: Unreachable code detected. throw new Error("") console.log("1") ~~~~~~~~~~~~~~~~ -!!! error TS7027: Unreachable code detected. console.log("2") + ~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. \ No newline at end of file diff --git a/tests/baselines/reference/reachabilityChecks9.errors.txt b/tests/baselines/reference/reachabilityChecks9.errors.txt index 6d51345ca2e0e..75b9cf98dac0e 100644 --- a/tests/baselines/reference/reachabilityChecks9.errors.txt +++ b/tests/baselines/reference/reachabilityChecks9.errors.txt @@ -11,8 +11,9 @@ reachabilityChecks9.ts(20,7): error TS7027: Unreachable code detected. return; console.log("1"); ~~~~~~~~~~~~~~~~~ -!!! error TS7027: Unreachable code detected. console.log("2"); + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. case "b": console.log("3"); } @@ -26,8 +27,9 @@ reachabilityChecks9.ts(20,7): error TS7027: Unreachable code detected. return; console.log("2"); ~~~~~~~~~~~~~~~~~ -!!! error TS7027: Unreachable code detected. console.log("3"); + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. case "b": console.log("4"); } diff --git a/tests/cases/fourslash/codeFixUnreachableCode_noSuggestionIfDisabled.ts b/tests/cases/fourslash/codeFixUnreachableCode_noSuggestionIfDisabled.ts index 35ad3c430a335..c83f66422d5e9 100644 --- a/tests/cases/fourslash/codeFixUnreachableCode_noSuggestionIfDisabled.ts +++ b/tests/cases/fourslash/codeFixUnreachableCode_noSuggestionIfDisabled.ts @@ -2,6 +2,12 @@ // @allowUnreachableCode: true -////if (false) 0; +////if (false) [|0;|] -verify.getSuggestionDiagnostics([]); +// Suggestions are returned, but turned into greyed-out text by the editor. +verify.getSuggestionDiagnostics(test.ranges().map((range): FourSlashInterface.Diagnostic => ({ + message: "Unreachable code detected.", + code: 7027, + reportsUnnecessary: true, + range, +}))); diff --git a/tests/cases/fourslash/codeFixUnusedLabel_noSuggestionIfDisabled.ts b/tests/cases/fourslash/codeFixUnusedLabel_noSuggestionIfDisabled.ts index a7e504d2f66dd..3d373334f3d33 100644 --- a/tests/cases/fourslash/codeFixUnusedLabel_noSuggestionIfDisabled.ts +++ b/tests/cases/fourslash/codeFixUnusedLabel_noSuggestionIfDisabled.ts @@ -2,6 +2,12 @@ // @allowUnusedLabels: true -////foo: while (true) {} +////[|foo|]: while (true) {} -verify.getSuggestionDiagnostics([]); +// Suggestions are returned, but turned into greyed-out text by the editor. +verify.getSuggestionDiagnostics(test.ranges().map((range): FourSlashInterface.Diagnostic => ({ + message: "Unused label.", + code: 7028, + reportsUnnecessary: true, + range, +}))); From 3f54dd22db7afb547650a236fe22e8af53e9fcbe Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:02:04 -0800 Subject: [PATCH 03/13] Even more simplfication --- src/compiler/binder.ts | 15 +++++++-------- src/compiler/checker.ts | 20 ++++++++++---------- src/compiler/utilities.ts | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 23ed4ea6517a1..d3333bc0c9fba 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -198,6 +198,7 @@ import { isParenthesizedExpression, isPartOfParameterDeclaration, isPartOfTypeQuery, + isPotentiallyExecutableNode, isPrefixUnaryExpression, isPrivateIdentifier, isPrologueDirective, @@ -1090,15 +1091,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { // and set it before we descend into nodes that could actually be part of an assignment pattern. inAssignmentPattern = false; - const isPotentiallyExecutableStatement = (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement) || - node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.EnumDeclaration || node.kind === SyntaxKind.ModuleDeclaration; - - if (isPotentiallyExecutableStatement && canHaveFlowNode(node)) { - node.flowNode = currentFlow; - } - if (currentFlow === unreachableFlow) { - if (isPotentiallyExecutableStatement) { + if (isPotentiallyExecutableNode(node)) { (node as Mutable).flags |= NodeFlags.Unreachable; } bindEachChild(node); @@ -1106,6 +1100,11 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { inAssignmentPattern = saveInAssignmentPattern; return; } + + if (SyntaxKind.FirstStatement <= node.kind && node.kind <= SyntaxKind.LastStatement && canHaveFlowNode(node)) { + node.flowNode = currentFlow; + } + switch (node.kind) { case SyntaxKind.WhileStatement: bindWhileStatement(node as WhileStatement); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 78ded541d9e69..51afde5d7a528 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -708,6 +708,7 @@ import { isPartOfTypeOnlyImportOrExportDeclaration, isPartOfTypeQuery, isPlainJsFile, + isPotentiallyExecutableNode, isPrefixUnaryExpression, isPrivateIdentifier, isPrivateIdentifierClassElementDeclaration, @@ -49199,9 +49200,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkSourceElementUnreachable(node: Node): boolean { - reportedUnreachableNodes ||= new Set(); + if (!isPotentiallyExecutableNode(node)) { + return false; + } - if (reportedUnreachableNodes.has(node)) { + if (reportedUnreachableNodes?.has(node)) { return true; } @@ -49209,7 +49212,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } - reportedUnreachableNodes.add(node); + (reportedUnreachableNodes ??= new Set()).add(node); const isError = unreachableCodeIsError(compilerOptions); const sourceFile = getSourceFileOfNode(node); @@ -49224,7 +49227,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (offset >= 0) { for (let i = offset + 1; i < statements.length; i++) { const nextNode = statements[i]; - if (!isSourceElementUnreachable(nextNode)) { + if (!isPotentiallyExecutableNode(nextNode) || !isSourceElementUnreachable(nextNode)) { break; } end = nextNode.end; @@ -49239,22 +49242,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isSourceElementUnreachable(node: Node): boolean { + // Precondition: isPotentiallyExecutableNode is true if (node.flags & NodeFlags.Unreachable) { switch (node.kind) { case SyntaxKind.EnumDeclaration: return !isEnumConst(node as EnumDeclaration) || shouldPreserveConstEnums(compilerOptions); case SyntaxKind.ModuleDeclaration: return isInstantiatedModule(node as ModuleDeclaration, shouldPreserveConstEnums(compilerOptions)); - case SyntaxKind.VariableStatement: - return !!(getCombinedNodeFlags((node as VariableStatement).declarationList) & NodeFlags.BlockScoped) || (node as VariableStatement).declarationList.declarations.some(d => d.initializer); default: return true; } } - else if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement) { - if (canHaveFlowNode(node) && node.flowNode) { - return !isReachableFlowNode(node.flowNode); - } + else if (canHaveFlowNode(node) && node.flowNode) { + return !isReachableFlowNode(node.flowNode); } return false; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9805b7ae9b1e4..8f9f812e6b9de 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -12372,3 +12372,17 @@ function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node function getFirstChild(node: Node): Node | undefined { return forEachChild(node, child => child); } + +/** @internal */ +export function isPotentiallyExecutableNode(node: Node): boolean { + if (SyntaxKind.FirstStatement <= node.kind && node.kind <= SyntaxKind.LastStatement) { + if (isVariableStatement(node)) { + if (getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) { + return true; + } + return some(node.declarationList.declarations, d => d.initializer !== undefined); + } + return true; + } + return isClassDeclaration(node) || isEnumDeclaration(node) || isModuleDeclaration(node); +} From 360da26db1b52204e416ebbc606962c2515dd752 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:22:22 -0800 Subject: [PATCH 04/13] Align with corsa --- src/compiler/checker.ts | 5 +++-- src/compiler/utilities.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 51afde5d7a528..c7c6676490749 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -62,6 +62,7 @@ import { canHaveLocals, canHaveModifiers, canHaveModuleSpecifier, + canHaveStatements, canHaveSymbol, canIncludeBindAndCheckDiagnostics, canUsePropertyAccess, @@ -49221,8 +49222,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let end = node.end; const parent = node.parent; - if (parent && (isBlock(parent) || isModuleBlock(parent) || isSourceFile(parent) || parent.kind === SyntaxKind.CaseClause || parent.kind === SyntaxKind.DefaultClause)) { - const statements = (parent as Block | ModuleBlock | SourceFile | CaseClause | DefaultClause).statements; + if (canHaveStatements(parent)) { + const statements = parent.statements; const offset = statements.indexOf(node as Statement); if (offset >= 0) { for (let i = offset + 1; i < statements.length; i++) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8f9f812e6b9de..5aaa29e929a49 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -261,7 +261,9 @@ import { isBinaryExpression, isBindingElement, isBindingPattern, + isBlock, isCallExpression, + isCaseClause, isClassDeclaration, isClassElement, isClassExpression, @@ -274,6 +276,7 @@ import { isDeclaration, isDeclarationFileName, isDecorator, + isDefaultClause, isElementAccessExpression, isEnumDeclaration, isEnumMember, @@ -332,6 +335,7 @@ import { isMethodDeclaration, isMethodOrAccessor, isModifierLike, + isModuleBlock, isModuleDeclaration, isModuleOrEnumDeclaration, isNamedDeclaration, @@ -603,6 +607,7 @@ import { WriteFileCallbackData, YieldExpression, } from "./_namespaces/ts.js"; +import Module from "module"; /** @internal */ export const resolvingEmptyArray: never[] = []; @@ -12373,6 +12378,11 @@ function getFirstChild(node: Node): Node | undefined { return forEachChild(node, child => child); } +/** @internal */ +export function canHaveStatements(node: Node): node is Block | ModuleBlock | SourceFile | CaseClause | DefaultClause { + return isBlock(node) || isModuleBlock(node) || isSourceFile(node) || isCaseClause(node) || isDefaultClause(node); +} + /** @internal */ export function isPotentiallyExecutableNode(node: Node): boolean { if (SyntaxKind.FirstStatement <= node.kind && node.kind <= SyntaxKind.LastStatement) { From c569b958e2d58c108b1a78e5619355ed67a9cacb Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:23:27 -0800 Subject: [PATCH 05/13] hello fmt my old friend --- src/compiler/utilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5aaa29e929a49..b687408db06e9 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1,3 +1,4 @@ +import Module from "module"; import { __String, AccessExpression, @@ -607,7 +608,6 @@ import { WriteFileCallbackData, YieldExpression, } from "./_namespaces/ts.js"; -import Module from "module"; /** @internal */ export const resolvingEmptyArray: never[] = []; From e18d9756bf62cf936ae4adb158e12ea623a12003 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:23:52 -0800 Subject: [PATCH 06/13] what --- src/compiler/utilities.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b687408db06e9..57ac050b68081 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1,4 +1,3 @@ -import Module from "module"; import { __String, AccessExpression, From 844960f7f7ca3b8d19d4c3e58ea273c030c2cf13 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:48:23 -0800 Subject: [PATCH 07/13] Add failing test --- .../reachabilityChecksIgnored.errors.txt | 27 ++++++++++ .../reference/reachabilityChecksIgnored.js | 28 ++++++++++ .../reachabilityChecksIgnored.symbols | 28 ++++++++++ .../reference/reachabilityChecksIgnored.types | 54 +++++++++++++++++++ .../compiler/reachabilityChecksIgnored.ts | 17 ++++++ 5 files changed, 154 insertions(+) create mode 100644 tests/baselines/reference/reachabilityChecksIgnored.errors.txt create mode 100644 tests/baselines/reference/reachabilityChecksIgnored.js create mode 100644 tests/baselines/reference/reachabilityChecksIgnored.symbols create mode 100644 tests/baselines/reference/reachabilityChecksIgnored.types create mode 100644 tests/cases/compiler/reachabilityChecksIgnored.ts diff --git a/tests/baselines/reference/reachabilityChecksIgnored.errors.txt b/tests/baselines/reference/reachabilityChecksIgnored.errors.txt new file mode 100644 index 0000000000000..391b526ef3e8b --- /dev/null +++ b/tests/baselines/reference/reachabilityChecksIgnored.errors.txt @@ -0,0 +1,27 @@ +reachabilityChecksIgnored.ts(4,5): error TS7027: Unreachable code detected. +reachabilityChecksIgnored.ts(11,5): error TS2578: Unused '@ts-expect-error' directive. +reachabilityChecksIgnored.ts(11,5): error TS7027: Unreachable code detected. + + +==== reachabilityChecksIgnored.ts (3 errors) ==== + function a() { + throw new Error(""); + + // @ts-ignore + ~~~~~~~~~~~~~ + console.log("unreachable"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + } + + function b() { + throw new Error(""); + + // @ts-expect-error + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2578: Unused '@ts-expect-error' directive. + ~~~~~~~~~~~~~~~~~~~ + console.log("unreachable"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + } \ No newline at end of file diff --git a/tests/baselines/reference/reachabilityChecksIgnored.js b/tests/baselines/reference/reachabilityChecksIgnored.js new file mode 100644 index 0000000000000..20ea3147dd9a5 --- /dev/null +++ b/tests/baselines/reference/reachabilityChecksIgnored.js @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/reachabilityChecksIgnored.ts] //// + +//// [reachabilityChecksIgnored.ts] +function a() { + throw new Error(""); + + // @ts-ignore + console.log("unreachable"); +} + +function b() { + throw new Error(""); + + // @ts-expect-error + console.log("unreachable"); +} + +//// [reachabilityChecksIgnored.js] +function a() { + throw new Error(""); + // @ts-ignore + console.log("unreachable"); +} +function b() { + throw new Error(""); + // @ts-expect-error + console.log("unreachable"); +} diff --git a/tests/baselines/reference/reachabilityChecksIgnored.symbols b/tests/baselines/reference/reachabilityChecksIgnored.symbols new file mode 100644 index 0000000000000..e41bf5334309a --- /dev/null +++ b/tests/baselines/reference/reachabilityChecksIgnored.symbols @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/reachabilityChecksIgnored.ts] //// + +=== reachabilityChecksIgnored.ts === +function a() { +>a : Symbol(a, Decl(reachabilityChecksIgnored.ts, 0, 0)) + + throw new Error(""); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + // @ts-ignore + console.log("unreachable"); +>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, --, --)) +} + +function b() { +>b : Symbol(b, Decl(reachabilityChecksIgnored.ts, 5, 1)) + + throw new Error(""); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + // @ts-expect-error + console.log("unreachable"); +>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, --, --)) +} diff --git a/tests/baselines/reference/reachabilityChecksIgnored.types b/tests/baselines/reference/reachabilityChecksIgnored.types new file mode 100644 index 0000000000000..8b1798d5ce288 --- /dev/null +++ b/tests/baselines/reference/reachabilityChecksIgnored.types @@ -0,0 +1,54 @@ +//// [tests/cases/compiler/reachabilityChecksIgnored.ts] //// + +=== reachabilityChecksIgnored.ts === +function a() { +>a : () => void +> : ^^^^^^^^^^ + + throw new Error(""); +>new Error("") : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +>"" : "" +> : ^^ + + // @ts-ignore + console.log("unreachable"); +>console.log("unreachable") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"unreachable" : "unreachable" +> : ^^^^^^^^^^^^^ +} + +function b() { +>b : () => void +> : ^^^^^^^^^^ + + throw new Error(""); +>new Error("") : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +>"" : "" +> : ^^ + + // @ts-expect-error + console.log("unreachable"); +>console.log("unreachable") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"unreachable" : "unreachable" +> : ^^^^^^^^^^^^^ +} diff --git a/tests/cases/compiler/reachabilityChecksIgnored.ts b/tests/cases/compiler/reachabilityChecksIgnored.ts new file mode 100644 index 0000000000000..baf32b57894d0 --- /dev/null +++ b/tests/cases/compiler/reachabilityChecksIgnored.ts @@ -0,0 +1,17 @@ +// @allowUnreachableCode: false +// @preserveConstEnums: true + + +function a() { + throw new Error(""); + + // @ts-ignore + console.log("unreachable"); +} + +function b() { + throw new Error(""); + + // @ts-expect-error + console.log("unreachable"); +} \ No newline at end of file From 9007c85649f16289dfba485359643756afcdb341 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:50:29 -0800 Subject: [PATCH 08/13] Fix start range --- src/compiler/checker.ts | 2 +- .../reachabilityChecksIgnored.errors.txt | 27 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 tests/baselines/reference/reachabilityChecksIgnored.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c7c6676490749..6debde4f2e865 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -49218,7 +49218,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const isError = unreachableCodeIsError(compilerOptions); const sourceFile = getSourceFileOfNode(node); - const start = skipTrivia(sourceFile.text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const start = skipTrivia(sourceFile.text, node.pos); let end = node.end; const parent = node.parent; diff --git a/tests/baselines/reference/reachabilityChecksIgnored.errors.txt b/tests/baselines/reference/reachabilityChecksIgnored.errors.txt deleted file mode 100644 index 391b526ef3e8b..0000000000000 --- a/tests/baselines/reference/reachabilityChecksIgnored.errors.txt +++ /dev/null @@ -1,27 +0,0 @@ -reachabilityChecksIgnored.ts(4,5): error TS7027: Unreachable code detected. -reachabilityChecksIgnored.ts(11,5): error TS2578: Unused '@ts-expect-error' directive. -reachabilityChecksIgnored.ts(11,5): error TS7027: Unreachable code detected. - - -==== reachabilityChecksIgnored.ts (3 errors) ==== - function a() { - throw new Error(""); - - // @ts-ignore - ~~~~~~~~~~~~~ - console.log("unreachable"); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS7027: Unreachable code detected. - } - - function b() { - throw new Error(""); - - // @ts-expect-error - ~~~~~~~~~~~~~~~~~~~ -!!! error TS2578: Unused '@ts-expect-error' directive. - ~~~~~~~~~~~~~~~~~~~ - console.log("unreachable"); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS7027: Unreachable code detected. - } \ No newline at end of file From ac83cc7d42595a74895fbd9d8582e0c6b388e835 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:18:50 -0800 Subject: [PATCH 09/13] Fix tristate --- src/compiler/checker.ts | 11 ++++------- src/compiler/utilities.ts | 10 ---------- .../codeFixUnreachableCode_noSuggestionIfDisabled.ts | 8 +------- .../codeFixUnusedLabel_noSuggestionIfDisabled.ts | 8 +------- 4 files changed, 6 insertions(+), 31 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6debde4f2e865..2e33ac96a8918 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1120,8 +1120,6 @@ import { UnionType, UnionTypeNode, UniqueESSymbolType, - unreachableCodeIsError, - unusedLabelIsError, usingSingleLineStringWriter, VariableDeclaration, VariableDeclarationList, @@ -46609,8 +46607,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { }); } - if (node.label.flags & NodeFlags.Unreachable) { - errorOrSuggestion(unusedLabelIsError(compilerOptions), node.label, Diagnostics.Unused_label); + if (node.label.flags & NodeFlags.Unreachable && compilerOptions.allowUnusedLabels !== true) { + errorOrSuggestion(compilerOptions.allowUnusedLabels === false, node.label, Diagnostics.Unused_label); } // ensure that label is unique @@ -49016,7 +49014,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - if (!withinUnreachableCode) { + if (compilerOptions.allowUnreachableCode !== true && !withinUnreachableCode) { if (checkSourceElementUnreachable(node)) { withinUnreachableCode = true; } @@ -49215,7 +49213,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { (reportedUnreachableNodes ??= new Set()).add(node); - const isError = unreachableCodeIsError(compilerOptions); const sourceFile = getSourceFileOfNode(node); const start = skipTrivia(sourceFile.text, node.pos); @@ -49237,7 +49234,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - addErrorOrSuggestion(isError, createFileDiagnostic(sourceFile, start, end - start, Diagnostics.Unreachable_code_detected)); + addErrorOrSuggestion(compilerOptions.allowUnreachableCode === false, createFileDiagnostic(sourceFile, start, end - start, Diagnostics.Unreachable_code_detected)); return true; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 57ac050b68081..cdf898ca8c3fc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -9277,16 +9277,6 @@ export function hasJsonModuleEmitEnabled(options: CompilerOptions): boolean { return true; } -/** @internal */ -export function unreachableCodeIsError(options: CompilerOptions): boolean { - return options.allowUnreachableCode === false; -} - -/** @internal */ -export function unusedLabelIsError(options: CompilerOptions): boolean { - return options.allowUnusedLabels === false; -} - /** @internal */ export function moduleResolutionSupportsPackageJsonExportsAndImports(moduleResolution: ModuleResolutionKind): boolean { return moduleResolution >= ModuleResolutionKind.Node16 && moduleResolution <= ModuleResolutionKind.NodeNext diff --git a/tests/cases/fourslash/codeFixUnreachableCode_noSuggestionIfDisabled.ts b/tests/cases/fourslash/codeFixUnreachableCode_noSuggestionIfDisabled.ts index c83f66422d5e9..8027f00275d96 100644 --- a/tests/cases/fourslash/codeFixUnreachableCode_noSuggestionIfDisabled.ts +++ b/tests/cases/fourslash/codeFixUnreachableCode_noSuggestionIfDisabled.ts @@ -4,10 +4,4 @@ ////if (false) [|0;|] -// Suggestions are returned, but turned into greyed-out text by the editor. -verify.getSuggestionDiagnostics(test.ranges().map((range): FourSlashInterface.Diagnostic => ({ - message: "Unreachable code detected.", - code: 7027, - reportsUnnecessary: true, - range, -}))); +verify.getSuggestionDiagnostics([]); diff --git a/tests/cases/fourslash/codeFixUnusedLabel_noSuggestionIfDisabled.ts b/tests/cases/fourslash/codeFixUnusedLabel_noSuggestionIfDisabled.ts index 3d373334f3d33..e1a69b5bf3198 100644 --- a/tests/cases/fourslash/codeFixUnusedLabel_noSuggestionIfDisabled.ts +++ b/tests/cases/fourslash/codeFixUnusedLabel_noSuggestionIfDisabled.ts @@ -4,10 +4,4 @@ ////[|foo|]: while (true) {} -// Suggestions are returned, but turned into greyed-out text by the editor. -verify.getSuggestionDiagnostics(test.ranges().map((range): FourSlashInterface.Diagnostic => ({ - message: "Unused label.", - code: 7028, - reportsUnnecessary: true, - range, -}))); +verify.getSuggestionDiagnostics([]); From 28244cb806d7a57d7c85c2f5d4f75d56dca8d4f5 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:15:04 -0800 Subject: [PATCH 10/13] Add TODO about range diagnostics, clear set --- src/compiler/checker.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2e33ac96a8918..586b0c90860d6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -49218,6 +49218,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const start = skipTrivia(sourceFile.text, node.pos); let end = node.end; + // TODO: if we are doing range diagnostics, it's possible we _haven't_ + // reported nodes before us in the same statement list. We should walk backwards + // and extend the range to include any prior unreachable unreported nodes as well. const parent = node.parent; if (canHaveStatements(parent)) { const statements = parent.statements; @@ -49455,6 +49458,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { performance.mark(afterMark); performance.measure("Check", beforeMark, afterMark); tracing?.pop(); + reportedUnreachableNodes = undefined; } function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { From 7e63fb9813ea207cd1fcaa9c604bd716eea6f8e3 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:00:05 -0800 Subject: [PATCH 11/13] Prevent region diagnostics from breaking --- src/compiler/checker.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 586b0c90860d6..bcefed5a60289 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -49215,28 +49215,43 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const sourceFile = getSourceFileOfNode(node); - const start = skipTrivia(sourceFile.text, node.pos); + let start = node.pos; let end = node.end; - // TODO: if we are doing range diagnostics, it's possible we _haven't_ - // reported nodes before us in the same statement list. We should walk backwards - // and extend the range to include any prior unreachable unreported nodes as well. const parent = node.parent; if (canHaveStatements(parent)) { const statements = parent.statements; const offset = statements.indexOf(node as Statement); if (offset >= 0) { + // Scan backwards to find the first unreachable unreported node; + // this may happen when producing region diagnostics where not all nodes + // will have been visited. + let first = offset; + for (let i = offset - 1; i >= 0; i--) { + const prevNode = statements[i]; + if (!isPotentiallyExecutableNode(prevNode) || reportedUnreachableNodes.has(prevNode) || !isSourceElementUnreachable(prevNode)) { + break; + } + first = i; + reportedUnreachableNodes.add(prevNode); + } + + let last = offset; for (let i = offset + 1; i < statements.length; i++) { const nextNode = statements[i]; if (!isPotentiallyExecutableNode(nextNode) || !isSourceElementUnreachable(nextNode)) { break; } - end = nextNode.end; + last = i; reportedUnreachableNodes.add(nextNode); } + + start = statements[first].pos; + end = statements[last].end; } } + start = skipTrivia(sourceFile.text, start); addErrorOrSuggestion(compilerOptions.allowUnreachableCode === false, createFileDiagnostic(sourceFile, start, end - start, Diagnostics.Unreachable_code_detected)); return true; From bf0feb297c6863eb894e44d722bebff1da8b36d7 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:48:44 -0800 Subject: [PATCH 12/13] Set flow to nil on unreachable --- src/compiler/binder.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d3333bc0c9fba..cfa409e878c82 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1092,6 +1092,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { inAssignmentPattern = false; if (currentFlow === unreachableFlow) { + if (canHaveFlowNode(node)) { + node.flowNode = undefined; + } if (isPotentiallyExecutableNode(node)) { (node as Mutable).flags |= NodeFlags.Unreachable; } From 4a0311e067c2e5497dfa55eb69a3e0b122d0bc14 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 13 Nov 2025 16:36:19 -0800 Subject: [PATCH 13/13] comments --- src/compiler/checker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bcefed5a60289..ba5765c0826a3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -49260,6 +49260,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isSourceElementUnreachable(node: Node): boolean { // Precondition: isPotentiallyExecutableNode is true if (node.flags & NodeFlags.Unreachable) { + // The binder has determined that this code is unreachable. + // Ignore const enums unless preserveConstEnums is set. switch (node.kind) { case SyntaxKind.EnumDeclaration: return !isEnumConst(node as EnumDeclaration) || shouldPreserveConstEnums(compilerOptions); @@ -49270,6 +49272,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else if (canHaveFlowNode(node) && node.flowNode) { + // For code the binder doesn't know is unreachable, use control flow / types. return !isReachableFlowNode(node.flowNode); } return false;