From ea338992e37b51be968eb50a5459e6b755eed67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 31 Aug 2023 19:20:19 +0200 Subject: [PATCH 1/2] Mark all unreachable executable statements in cases and source files --- src/compiler/binder.ts | 3 +- src/compiler/types.ts | 19 ++-- src/compiler/utilities.ts | 14 +++ tests/baselines/reference/api/typescript.d.ts | 18 ++-- .../reference/reachabilityChecks1.errors.txt | 42 ++++++++- .../reference/reachabilityChecks10.symbols | 16 ++++ .../reference/reachabilityChecks10.types | 22 +++++ .../reference/reachabilityChecks9.symbols | 65 ++++++++++++++ .../reference/reachabilityChecks9.types | 87 +++++++++++++++++++ tests/cases/compiler/reachabilityChecks10.ts | 6 ++ tests/cases/compiler/reachabilityChecks9.ts | 28 ++++++ 11 files changed, 297 insertions(+), 23 deletions(-) create mode 100644 tests/baselines/reference/reachabilityChecks10.symbols create mode 100644 tests/baselines/reference/reachabilityChecks10.types 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/reachabilityChecks9.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 38127a8d62075..515c49798542e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -213,6 +213,7 @@ import { isSpecialPropertyDeclaration, isStatement, isStatementButNotDeclaration, + isStatementsContainer, isStatic, isString, isStringLiteralLike, @@ -3729,7 +3730,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void): void { - if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) { + if (isStatement(node) && isExecutableStatement(node) && isStatementsContainer(node.parent)) { const { statements } = node.parent; const slice = sliceAfter(statements, node); getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1])); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 14575e7f1733d..e5bc5fadf2e15 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3230,6 +3230,10 @@ export interface Statement extends Node, JSDocContainer { _statementBrand: any; } +export interface StatementsContainer extends Node { + readonly statements: NodeArray; +} + // Represents a statement that is elided as part of a transformation to emit comments on a // not-emitted node. export interface NotEmittedStatement extends Statement { @@ -3273,9 +3277,8 @@ export type BlockLike = | ModuleBlock | CaseOrDefaultClause; -export interface Block extends Statement, LocalsContainer { +export interface Block extends Statement, LocalsContainer, StatementsContainer { readonly kind: SyntaxKind.Block; - readonly statements: NodeArray; /** @internal */ multiLine?: boolean; } @@ -3382,18 +3385,16 @@ export interface CaseBlock extends Node, LocalsContainer { readonly clauses: NodeArray; } -export interface CaseClause extends Node, JSDocContainer { +export interface CaseClause extends StatementsContainer, JSDocContainer { readonly kind: SyntaxKind.CaseClause; readonly parent: CaseBlock; readonly expression: Expression; - readonly statements: NodeArray; /** @internal */ fallthroughFlowNode?: FlowNode; } -export interface DefaultClause extends Node { +export interface DefaultClause extends StatementsContainer { readonly kind: SyntaxKind.DefaultClause; readonly parent: CaseBlock; - readonly statements: NodeArray; /** @internal */ fallthroughFlowNode?: FlowNode; } @@ -3558,10 +3559,9 @@ export interface JSDocNamespaceDeclaration extends ModuleDeclaration { readonly body?: JSDocNamespaceBody; } -export interface ModuleBlock extends Node, Statement { +export interface ModuleBlock extends StatementsContainer, Statement { readonly kind: SyntaxKind.ModuleBlock; readonly parent: ModuleDeclaration; - readonly statements: NodeArray; } export type ModuleReference = @@ -4150,9 +4150,8 @@ export interface RedirectInfo { export type ResolutionMode = ModuleKind.ESNext | ModuleKind.CommonJS | undefined; // Source files are declarations when they are external modules. -export interface SourceFile extends Declaration, LocalsContainer { +export interface SourceFile extends Declaration, LocalsContainer, StatementsContainer { readonly kind: SyntaxKind.SourceFile; - readonly statements: NodeArray; readonly endOfFileToken: Token; fileName: string; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 960494f8a9abe..7519c3fb31486 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -492,6 +492,7 @@ import { startsWith, startsWithUseStrict, Statement, + StatementsContainer, StringLiteral, StringLiteralLike, StringLiteralType, @@ -4619,6 +4620,19 @@ export function isNodeWithPossibleHoistedDeclaration(node: Node): node is NodeWi return false; } +/** @internal */ +export function isStatementsContainer(node: Node): node is StatementsContainer { + switch (node.kind) { + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + case SyntaxKind.SourceFile: + case SyntaxKind.DefaultClause: + case SyntaxKind.CaseClause: + return true; + } + return false; +} + /** @internal */ export type ValueSignatureDeclaration = | FunctionDeclaration diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index dcea4be64289a..c7ae3cf30c0fa 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5693,6 +5693,9 @@ declare namespace ts { interface Statement extends Node, JSDocContainer { _statementBrand: any; } + interface StatementsContainer extends Node { + readonly statements: NodeArray; + } interface NotEmittedStatement extends Statement { readonly kind: SyntaxKind.NotEmittedStatement; } @@ -5714,9 +5717,8 @@ declare namespace ts { readonly name?: Identifier; } type BlockLike = SourceFile | Block | ModuleBlock | CaseOrDefaultClause; - interface Block extends Statement, LocalsContainer { + interface Block extends Statement, LocalsContainer, StatementsContainer { readonly kind: SyntaxKind.Block; - readonly statements: NodeArray; } interface VariableStatement extends Statement, FlowContainer { readonly kind: SyntaxKind.VariableStatement; @@ -5792,16 +5794,14 @@ declare namespace ts { readonly parent: SwitchStatement; readonly clauses: NodeArray; } - interface CaseClause extends Node, JSDocContainer { + interface CaseClause extends StatementsContainer, JSDocContainer { readonly kind: SyntaxKind.CaseClause; readonly parent: CaseBlock; readonly expression: Expression; - readonly statements: NodeArray; } - interface DefaultClause extends Node { + interface DefaultClause extends StatementsContainer { readonly kind: SyntaxKind.DefaultClause; readonly parent: CaseBlock; - readonly statements: NodeArray; } type CaseOrDefaultClause = CaseClause | DefaultClause; interface LabeledStatement extends Statement, FlowContainer { @@ -5907,10 +5907,9 @@ declare namespace ts { readonly name: Identifier; readonly body?: JSDocNamespaceBody; } - interface ModuleBlock extends Node, Statement { + interface ModuleBlock extends StatementsContainer, Statement { readonly kind: SyntaxKind.ModuleBlock; readonly parent: ModuleDeclaration; - readonly statements: NodeArray; } type ModuleReference = EntityName | ExternalModuleReference; /** @@ -6364,9 +6363,8 @@ declare namespace ts { getLineAndCharacterOfPosition(pos: number): LineAndCharacter; } type ResolutionMode = ModuleKind.ESNext | ModuleKind.CommonJS | undefined; - interface SourceFile extends Declaration, LocalsContainer { + interface SourceFile extends Declaration, LocalsContainer, StatementsContainer { readonly kind: SyntaxKind.SourceFile; - readonly statements: NodeArray; readonly endOfFileToken: Token; fileName: string; text: string; diff --git a/tests/baselines/reference/reachabilityChecks1.errors.txt b/tests/baselines/reference/reachabilityChecks1.errors.txt index 7273652609490..89b710cee5aad 100644 --- a/tests/baselines/reference/reachabilityChecks1.errors.txt +++ b/tests/baselines/reference/reachabilityChecks1.errors.txt @@ -3,54 +3,86 @@ 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 (5 errors) ==== +==== reachabilityChecks1.ts (6 errors) ==== while (true); var x = 1; ~~~~~~~~~~ -!!! error TS7027: Unreachable code detected. + 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. } + ~ +!!! error TS7027: Unreachable code detected. function f1(x) { if (x) { @@ -72,10 +104,16 @@ reachabilityChecks1.ts(47,5): error TS7027: Unreachable code detected. } module B { + ~~~~~~~~~~ for (; ;); + ~~~~~~~~~~~~~~ module C { + ~~~~~~~~~~~~~~ } + ~~~~~ } + ~ +!!! error TS7027: Unreachable code detected. function f3() { do { 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..d57914a48052c --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks10.types @@ -0,0 +1,22 @@ +//// [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/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..8bced2f44cca7 --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks9.types @@ -0,0 +1,87 @@ +//// [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..56baa4f2321cb --- /dev/null +++ b/tests/cases/compiler/reachabilityChecks10.ts @@ -0,0 +1,6 @@ +// @strict: true +// @noEmit: true + +throw new Error("") +console.log("1") +console.log("2") diff --git a/tests/cases/compiler/reachabilityChecks9.ts b/tests/cases/compiler/reachabilityChecks9.ts new file mode 100644 index 0000000000000..5e797686a6430 --- /dev/null +++ b/tests/cases/compiler/reachabilityChecks9.ts @@ -0,0 +1,28 @@ +// @strict: true +// @noEmit: true + +// 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 f864286ad908fe06b2f18f519e3028c5431803d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 13 Nov 2025 09:26:07 +0100 Subject: [PATCH 2/2] use the relevant compiler option in the tests --- .../reference/reachabilityChecks10.errors.txt | 11 ++++++ .../reference/reachabilityChecks9.errors.txt | 37 +++++++++++++++++++ tests/cases/compiler/reachabilityChecks10.ts | 1 + tests/cases/compiler/reachabilityChecks9.ts | 1 + 4 files changed, 50 insertions(+) create mode 100644 tests/baselines/reference/reachabilityChecks10.errors.txt create mode 100644 tests/baselines/reference/reachabilityChecks9.errors.txt diff --git a/tests/baselines/reference/reachabilityChecks10.errors.txt b/tests/baselines/reference/reachabilityChecks10.errors.txt new file mode 100644 index 0000000000000..2b26eae20f99e --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks10.errors.txt @@ -0,0 +1,11 @@ +reachabilityChecks10.ts(2,1): error TS7027: Unreachable code detected. + + +==== reachabilityChecks10.ts (1 errors) ==== + throw new Error("") + console.log("1") + ~~~~~~~~~~~~~~~~ + 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 new file mode 100644 index 0000000000000..75b9cf98dac0e --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks9.errors.txt @@ -0,0 +1,37 @@ +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"); + ~~~~~~~~~~~~~~~~~ + console.log("2"); + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + case "b": + console.log("3"); + } + } + + function h(str: string) { + switch (str) { + case "a": + console.log("1"); + default: + return; + console.log("2"); + ~~~~~~~~~~~~~~~~~ + console.log("3"); + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS7027: Unreachable code detected. + case "b": + console.log("4"); + } + } + \ No newline at end of file diff --git a/tests/cases/compiler/reachabilityChecks10.ts b/tests/cases/compiler/reachabilityChecks10.ts index 56baa4f2321cb..536b3ad5d8b8c 100644 --- a/tests/cases/compiler/reachabilityChecks10.ts +++ b/tests/cases/compiler/reachabilityChecks10.ts @@ -1,4 +1,5 @@ // @strict: true +// @allowUnreachableCode: false // @noEmit: true throw new Error("") diff --git a/tests/cases/compiler/reachabilityChecks9.ts b/tests/cases/compiler/reachabilityChecks9.ts index 5e797686a6430..4c577a203a29e 100644 --- a/tests/cases/compiler/reachabilityChecks9.ts +++ b/tests/cases/compiler/reachabilityChecks9.ts @@ -1,4 +1,5 @@ // @strict: true +// @allowUnreachableCode: false // @noEmit: true // https://github.com/microsoft/TypeScript/issues/55562