Skip to content

Commit 3ed2e8e

Browse files
Report unreachable on enums (#58380)
Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent 3a43940 commit 3ed2e8e

12 files changed

+2128
-26
lines changed

src/compiler/binder.ts

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ import {
154154
isEmptyObjectLiteral,
155155
isEntityNameExpression,
156156
isEnumConst,
157-
isEnumDeclaration,
158157
isExportAssignment,
159158
isExportDeclaration,
160159
isExportsIdentifier,
@@ -3789,7 +3788,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
37893788
(isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) ||
37903789
// report error on class declarations
37913790
node.kind === SyntaxKind.ClassDeclaration ||
3792-
// report error on instantiated modules or const-enums only modules if preserveConstEnums is set
3791+
// report errors on enums with preserved emit
3792+
isEnumDeclarationWithPreservedEmit(node, options) ||
3793+
// report error on instantiated modules
37933794
(node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(node as ModuleDeclaration));
37943795

37953796
if (reportError) {
@@ -3813,15 +3814,19 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
38133814
node.declarationList.declarations.some(d => !!d.initializer)
38143815
);
38153816

3816-
eachUnreachableRange(node, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected));
3817+
eachUnreachableRange(node, options, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected));
38173818
}
38183819
}
38193820
}
38203821
return true;
38213822
}
38223823
}
38233824

3824-
function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void): void {
3825+
function isEnumDeclarationWithPreservedEmit(node: Node, options: CompilerOptions): boolean {
3826+
return node.kind === SyntaxKind.EnumDeclaration && (!isEnumConst(node as EnumDeclaration) || shouldPreserveConstEnums(options));
3827+
}
3828+
3829+
function eachUnreachableRange(node: Node, options: CompilerOptions, cb: (start: Node, last: Node) => void): void {
38253830
if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) {
38263831
const { statements } = node.parent;
38273832
const slice = sliceAfter(statements, node);
@@ -3830,26 +3835,27 @@ function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void)
38303835
else {
38313836
cb(node, node);
38323837
}
3833-
}
3834-
// As opposed to a pure declaration like an `interface`
3835-
function isExecutableStatement(s: Statement): boolean {
3836-
// Don't remove statements that can validly be used before they appear.
3837-
return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) &&
3838-
// `var x;` may declare a variable used above
3839-
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.BlockScoped)) && s.declarationList.declarations.some(d => !d.initializer));
3840-
}
38413838

3842-
function isPurelyTypeDeclaration(s: Statement): boolean {
3843-
switch (s.kind) {
3844-
case SyntaxKind.InterfaceDeclaration:
3845-
case SyntaxKind.TypeAliasDeclaration:
3846-
return true;
3847-
case SyntaxKind.ModuleDeclaration:
3848-
return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated;
3849-
case SyntaxKind.EnumDeclaration:
3850-
return hasSyntacticModifier(s, ModifierFlags.Const);
3851-
default:
3852-
return false;
3839+
// As opposed to a pure declaration like an `interface`
3840+
function isExecutableStatement(s: Statement): boolean {
3841+
// Don't remove statements that can validly be used before they appear.
3842+
return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) &&
3843+
// `var x;` may declare a variable used above
3844+
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.BlockScoped)) && s.declarationList.declarations.some(d => !d.initializer));
3845+
}
3846+
3847+
function isPurelyTypeDeclaration(s: Statement): boolean {
3848+
switch (s.kind) {
3849+
case SyntaxKind.InterfaceDeclaration:
3850+
case SyntaxKind.TypeAliasDeclaration:
3851+
return true;
3852+
case SyntaxKind.ModuleDeclaration:
3853+
return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated;
3854+
case SyntaxKind.EnumDeclaration:
3855+
return !isEnumDeclarationWithPreservedEmit(s, options);
3856+
default:
3857+
return false;
3858+
}
38533859
}
38543860
}
38553861

tests/baselines/reference/reachabilityChecks1.errors.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ reachabilityChecks1.ts(6,5): error TS7027: Unreachable code detected.
33
reachabilityChecks1.ts(18,5): error TS7027: Unreachable code detected.
44
reachabilityChecks1.ts(30,5): error TS7027: Unreachable code detected.
55
reachabilityChecks1.ts(47,5): error TS7027: Unreachable code detected.
6+
reachabilityChecks1.ts(60,5): error TS7027: Unreachable code detected.
7+
reachabilityChecks1.ts(69,5): error TS7027: Unreachable code detected.
68

79

8-
==== reachabilityChecks1.ts (5 errors) ====
10+
==== reachabilityChecks1.ts (7 errors) ====
911
while (true);
1012
var x = 1;
1113
~~~~~~~~~~
@@ -81,17 +83,25 @@ reachabilityChecks1.ts(47,5): error TS7027: Unreachable code detected.
8183
do {
8284
} while (true);
8385
enum E {
86+
~~~~~~~~
8487
X = 1
88+
~~~~~~~~~~~~~
8589
}
90+
~~~~~
91+
!!! error TS7027: Unreachable code detected.
8692
}
8793

8894
function f4() {
8995
if (true) {
9096
throw new Error();
9197
}
9298
const enum E {
99+
~~~~~~~~~~~~~~
93100
X = 1
101+
~~~~~~~~~~~~~
94102
}
103+
~~~~~
104+
!!! error TS7027: Unreachable code detected.
95105
}
96106

97107

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
unreachableDeclarations.ts(4,17): error TS2450: Enum 'EnumA' used before its declaration.
2+
unreachableDeclarations.ts(14,5): error TS7027: Unreachable code detected.
3+
unreachableDeclarations.ts(21,17): error TS2450: Enum 'EnumA' used before its declaration.
4+
unreachableDeclarations.ts(29,5): error TS7027: Unreachable code detected.
5+
unreachableDeclarations.ts(49,17): error TS2449: Class 'ClassA' used before its declaration.
6+
unreachableDeclarations.ts(57,5): error TS7027: Unreachable code detected.
7+
unreachableDeclarations.ts(63,14): error TS2450: Enum 'Bar' used before its declaration.
8+
unreachableDeclarations.ts(64,14): error TS2448: Block-scoped variable 'blah' used before its declaration.
9+
unreachableDeclarations.ts(64,14): error TS2454: Variable 'blah' is used before being assigned.
10+
unreachableDeclarations.ts(65,18): error TS2449: Class 'Foo' used before its declaration.
11+
unreachableDeclarations.ts(78,2): error TS7027: Unreachable code detected.
12+
unreachableDeclarations.ts(84,2): error TS1235: A namespace declaration is only allowed at the top level of a namespace or module.
13+
14+
15+
==== unreachableDeclarations.ts (12 errors) ====
16+
function func1() {
17+
aFunc();
18+
19+
console.log(EnumA.Value);
20+
~~~~~
21+
!!! error TS2450: Enum 'EnumA' used before its declaration.
22+
!!! related TS2728 unreachableDeclarations.ts:14:10: 'EnumA' is declared here.
23+
console.log(EnumB.Value);
24+
25+
return;
26+
27+
function aFunc() {
28+
console.log(EnumA.Value);
29+
console.log(EnumB.Value);
30+
}
31+
32+
enum EnumA { Value }
33+
~~~~~~~~~~~~~~~~~~~~
34+
!!! error TS7027: Unreachable code detected.
35+
const enum EnumB { Value }
36+
}
37+
38+
function func2() {
39+
aFunc();
40+
41+
console.log(EnumA.Value);
42+
~~~~~
43+
!!! error TS2450: Enum 'EnumA' used before its declaration.
44+
!!! related TS2728 unreachableDeclarations.ts:29:10: 'EnumA' is declared here.
45+
46+
return;
47+
48+
function aFunc() {
49+
console.log(EnumA.Value);
50+
}
51+
52+
enum EnumA { Value }
53+
~~~~~~~~~~~~~~~~~~~~
54+
!!! error TS7027: Unreachable code detected.
55+
}
56+
57+
function func3() {
58+
aFunc();
59+
60+
console.log(EnumB.Value);
61+
62+
return;
63+
64+
function aFunc() {
65+
console.log(EnumB.Value);
66+
}
67+
68+
const enum EnumB { Value }
69+
}
70+
71+
function func4() {
72+
aFunc();
73+
74+
console.log(ClassA.Value);
75+
~~~~~~
76+
!!! error TS2449: Class 'ClassA' used before its declaration.
77+
!!! related TS2728 unreachableDeclarations.ts:57:11: 'ClassA' is declared here.
78+
79+
return;
80+
81+
function aFunc() {
82+
console.log(ClassA.Value);
83+
}
84+
85+
class ClassA { static Value = 1234; }
86+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
87+
!!! error TS7027: Unreachable code detected.
88+
}
89+
90+
function func5() {
91+
aFunc();
92+
93+
console.log(Bar.A);
94+
~~~
95+
!!! error TS2450: Enum 'Bar' used before its declaration.
96+
!!! related TS2728 unreachableDeclarations.ts:80:7: 'Bar' is declared here.
97+
console.log(blah.prop);
98+
~~~~
99+
!!! error TS2448: Block-scoped variable 'blah' used before its declaration.
100+
!!! related TS2728 unreachableDeclarations.ts:78:8: 'blah' is declared here.
101+
~~~~
102+
!!! error TS2454: Variable 'blah' is used before being assigned.
103+
console.log(new Foo())
104+
~~~
105+
!!! error TS2449: Class 'Foo' used before its declaration.
106+
!!! related TS2728 unreachableDeclarations.ts:82:8: 'Foo' is declared here.
107+
console.log(Baz.value);
108+
109+
110+
return;
111+
112+
function aFunc() {
113+
console.log(Bar.A);
114+
console.log(blah.prop);
115+
console.log(new Foo())
116+
console.log(Baz.value);
117+
}
118+
119+
const blah = { prop: 1234 };
120+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
121+
122+
123+
enum Bar { A }
124+
~~~~~~~~~~~~~~~
125+
126+
127+
class Foo { x = 1234 }
128+
~~~~~~~~~~~~~~~~~~~~~~~
129+
130+
131+
namespace Baz { export const value = 1234 }
132+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
133+
!!! error TS7027: Unreachable code detected.
134+
~~~~~~~~~
135+
!!! error TS1235: A namespace declaration is only allowed at the top level of a namespace or module.
136+
}
137+

0 commit comments

Comments
 (0)