Skip to content

Commit fb15e9c

Browse files
committed
Merge pull request #4788 from Microsoft/reachabilityChecks
initial revision of reachability checks
2 parents 3e63144 + 3f11c0b commit fb15e9c

File tree

415 files changed

+5603
-4337
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

415 files changed

+5603
-4337
lines changed

Diff for: src/compiler/binder.ts

+454-30
Large diffs are not rendered by default.

Diff for: src/compiler/checker.ts

+24-32
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ namespace ts {
160160
let getGlobalPromiseConstructorLikeType: () => ObjectType;
161161
let getGlobalThenableType: () => ObjectType;
162162

163+
let jsxElementClassType: Type;
164+
163165
let tupleTypes: Map<TupleType> = {};
164166
let unionTypes: Map<UnionType> = {};
165167
let intersectionTypes: Map<IntersectionType> = {};
@@ -7874,7 +7876,6 @@ namespace ts {
78747876
return prop || unknownSymbol;
78757877
}
78767878

7877-
let jsxElementClassType: Type = undefined;
78787879
function getJsxGlobalElementClassType(): Type {
78797880
if (!jsxElementClassType) {
78807881
jsxElementClassType = getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.ElementClass);
@@ -9620,21 +9621,11 @@ namespace ts {
96209621
return aggregatedTypes;
96219622
}
96229623

9623-
function bodyContainsAReturnStatement(funcBody: Block) {
9624-
return forEachReturnStatement(funcBody, returnStatement => {
9625-
return true;
9626-
});
9627-
}
9628-
9629-
function bodyContainsSingleThrowStatement(body: Block) {
9630-
return (body.statements.length === 1) && (body.statements[0].kind === SyntaxKind.ThrowStatement);
9631-
}
9632-
96339624
// TypeScript Specification 1.0 (6.3) - July 2014
96349625
// An explicitly typed function whose return type isn't the Void or the Any type
96359626
// must have at least one return statement somewhere in its body.
96369627
// An exception to this rule is if the function implementation consists of a single 'throw' statement.
9637-
function checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(func: FunctionLikeDeclaration, returnType: Type): void {
9628+
function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration, returnType: Type): void {
96389629
if (!produceDiagnostics) {
96399630
return;
96409631
}
@@ -9645,26 +9636,20 @@ namespace ts {
96459636
}
96469637

96479638
// If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check.
9648-
if (nodeIsMissing(func.body) || func.body.kind !== SyntaxKind.Block) {
9639+
// also if HasImplicitReturnValue flags is not set this means that all codepaths in function body end with return of throw
9640+
if (nodeIsMissing(func.body) || func.body.kind !== SyntaxKind.Block || !(func.flags & NodeFlags.HasImplicitReturn)) {
96499641
return;
96509642
}
96519643

9652-
let bodyBlock = <Block>func.body;
9653-
9654-
// Ensure the body has at least one return expression.
9655-
if (bodyContainsAReturnStatement(bodyBlock)) {
9656-
return;
9644+
if (func.flags & NodeFlags.HasExplicitReturn) {
9645+
if (compilerOptions.noImplicitReturns) {
9646+
error(func.type, Diagnostics.Not_all_code_paths_return_a_value);
9647+
}
96579648
}
9658-
9659-
// If there are no return expressions, then we need to check if
9660-
// the function body consists solely of a throw statement;
9661-
// this is to make an exception for unimplemented functions.
9662-
if (bodyContainsSingleThrowStatement(bodyBlock)) {
9663-
return;
9649+
else {
9650+
// This function does not conform to the specification.
9651+
error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
96649652
}
9665-
9666-
// This function does not conform to the specification.
9667-
error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value_or_consist_of_a_single_throw_statement);
96689653
}
96699654

96709655
function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | MethodDeclaration, contextualMapper?: TypeMapper): Type {
@@ -9744,7 +9729,7 @@ namespace ts {
97449729
}
97459730

97469731
if (returnType && !node.asteriskToken) {
9747-
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, isAsync ? promisedType : returnType);
9732+
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, isAsync ? promisedType : returnType);
97489733
}
97499734

97509735
if (node.body) {
@@ -10945,8 +10930,15 @@ namespace ts {
1094510930
checkGrammarFunctionLikeDeclaration(node) || checkGrammarAccessor(node) || checkGrammarComputedPropertyName(node.name);
1094610931

1094710932
if (node.kind === SyntaxKind.GetAccessor) {
10948-
if (!isInAmbientContext(node) && nodeIsPresent(node.body) && !(bodyContainsAReturnStatement(<Block>node.body) || bodyContainsSingleThrowStatement(<Block>node.body))) {
10949-
error(node.name, Diagnostics.A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement);
10933+
if (!isInAmbientContext(node) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) {
10934+
if (node.flags & NodeFlags.HasExplicitReturn) {
10935+
if (compilerOptions.noImplicitReturns) {
10936+
error(node.name, Diagnostics.Not_all_code_paths_return_a_value);
10937+
}
10938+
}
10939+
else {
10940+
error(node.name, Diagnostics.A_get_accessor_must_return_a_value);
10941+
}
1095010942
}
1095110943
}
1095210944

@@ -11877,7 +11869,7 @@ namespace ts {
1187711869
promisedType = checkAsyncFunctionReturnType(node);
1187811870
}
1187911871

11880-
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, isAsync ? promisedType : returnType);
11872+
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, isAsync ? promisedType : returnType);
1188111873
}
1188211874

1188311875
if (produceDiagnostics && !node.type) {
@@ -14915,7 +14907,7 @@ namespace ts {
1491514907
function initializeTypeChecker() {
1491614908
// Bind all source files and propagate errors
1491714909
forEach(host.getSourceFiles(), file => {
14918-
bindSourceFile(file);
14910+
bindSourceFile(file, compilerOptions);
1491914911
});
1492014912

1492114913
// Initialize global symbol table

Diff for: src/compiler/commandLineParser.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,31 @@ namespace ts {
248248
description: Diagnostics.Specifies_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6,
249249
error: Diagnostics.Argument_for_moduleResolution_option_must_be_node_or_classic,
250250
},
251+
{
252+
name: "allowUnusedLabels",
253+
type: "boolean",
254+
description: Diagnostics.Do_not_report_errors_on_unused_labels
255+
},
256+
{
257+
name: "noImplicitReturns",
258+
type: "boolean",
259+
description: Diagnostics.Report_error_when_not_all_code_paths_in_function_return_a_value
260+
},
261+
{
262+
name: "noFallthroughCasesInSwitch",
263+
type: "boolean",
264+
description: Diagnostics.Report_errors_for_fallthrough_cases_in_switch_statement
265+
},
266+
{
267+
name: "allowUnreachableCode",
268+
type: "boolean",
269+
description: Diagnostics.Do_not_report_errors_on_unreachable_code
270+
},
251271
{
252272
name: "forceConsistentCasingInFileNames",
253273
type: "boolean",
254274
description: Diagnostics.Disallow_inconsistently_cased_references_to_the_same_file
255-
},
275+
}
256276
];
257277

258278
/* @internal */

Diff for: src/compiler/diagnosticMessages.json

+34-4
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,7 @@
10121012
"category": "Error",
10131013
"code": 2354
10141014
},
1015-
"A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.": {
1015+
"A function whose declared type is neither 'void' nor 'any' must return a value.": {
10161016
"category": "Error",
10171017
"code": 2355
10181018
},
@@ -1096,7 +1096,7 @@
10961096
"category": "Error",
10971097
"code": 2377
10981098
},
1099-
"A 'get' accessor must return a value or consist of a single 'throw' statement.": {
1099+
"A 'get' accessor must return a value.": {
11001100
"category": "Error",
11011101
"code": 2378
11021102
},
@@ -2298,6 +2298,22 @@
22982298
"category": "Message",
22992299
"code": 6073
23002300
},
2301+
"Do not report errors on unused labels.": {
2302+
"category": "Message",
2303+
"code": 6074
2304+
},
2305+
"Report error when not all code paths in function return a value.": {
2306+
"category": "Message",
2307+
"code": 6075
2308+
},
2309+
"Report errors for fallthrough cases in switch statement.": {
2310+
"category": "Message",
2311+
"code": 6076
2312+
},
2313+
"Do not report errors on unreachable code.": {
2314+
"category": "Message",
2315+
"code": 6077
2316+
},
23012317
"Variable '{0}' implicitly has an '{1}' type.": {
23022318
"category": "Error",
23032319
"code": 7005
@@ -2366,8 +2382,22 @@
23662382
"category": "Error",
23672383
"code": 7026
23682384
},
2369-
2370-
2385+
"Unreachable code detected.": {
2386+
"category": "Error",
2387+
"code": 7027
2388+
},
2389+
"Unused label.": {
2390+
"category": "Error",
2391+
"code": 7028
2392+
},
2393+
"Fallthrough case in switch.": {
2394+
"category": "Error",
2395+
"code": 7029
2396+
},
2397+
"Not all code paths return a value.": {
2398+
"category": "Error",
2399+
"code": 7030
2400+
},
23712401
"You cannot rename this element.": {
23722402
"category": "Error",
23732403
"code": 8000

Diff for: src/compiler/emitter.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ namespace ts {
99

1010
type DependencyGroup = Array<ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration>;
1111

12+
const enum Jump {
13+
Break = 1 << 1,
14+
Continue = 1 << 2,
15+
Return = 1 << 3
16+
}
17+
1218
let entities: Map<number> = {
1319
"quot": 0x0022,
1420
"amp": 0x0026,
@@ -371,12 +377,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
371377
return true;
372378
}
373379

374-
const enum Jump {
375-
Break = 1 << 1,
376-
Continue = 1 << 2,
377-
Return = 1 << 3
378-
}
379-
380380
interface ConvertedLoopState {
381381
/*
382382
* set of labels that occured inside the converted loop

Diff for: src/compiler/types.ts

+27-20
Original file line numberDiff line numberDiff line change
@@ -367,28 +367,31 @@ namespace ts {
367367

368368
export const enum NodeFlags {
369369
None = 0,
370-
Export = 0x00000001, // Declarations
371-
Ambient = 0x00000002, // Declarations
372-
Public = 0x00000010, // Property/Method
373-
Private = 0x00000020, // Property/Method
374-
Protected = 0x00000040, // Property/Method
375-
Static = 0x00000080, // Property/Method
376-
Abstract = 0x00000100, // Class/Method/ConstructSignature
377-
Async = 0x00000200, // Property/Method/Function
378-
Default = 0x00000400, // Function/Class (export default declaration)
379-
MultiLine = 0x00000800, // Multi-line array or object literal
380-
Synthetic = 0x00001000, // Synthetic node (for full fidelity)
381-
DeclarationFile = 0x00002000, // Node is a .d.ts file
382-
Let = 0x00004000, // Variable declaration
383-
Const = 0x00008000, // Variable declaration
384-
OctalLiteral = 0x00010000, // Octal numeric literal
385-
Namespace = 0x00020000, // Namespace declaration
386-
ExportContext = 0x00040000, // Export context (initialized by binding)
387-
ContainsThis = 0x00080000, // Interface contains references to "this"
388-
370+
Export = 1 << 1, // Declarations
371+
Ambient = 1 << 2, // Declarations
372+
Public = 1 << 3, // Property/Method
373+
Private = 1 << 4, // Property/Method
374+
Protected = 1 << 5, // Property/Method
375+
Static = 1 << 6, // Property/Method
376+
Abstract = 1 << 7, // Class/Method/ConstructSignature
377+
Async = 1 << 8, // Property/Method/Function
378+
Default = 1 << 9, // Function/Class (export default declaration)
379+
MultiLine = 1 << 10, // Multi-line array or object literal
380+
Synthetic = 1 << 11, // Synthetic node (for full fidelity)
381+
DeclarationFile = 1 << 12, // Node is a .d.ts file
382+
Let = 1 << 13, // Variable declaration
383+
Const = 1 << 14, // Variable declaration
384+
OctalLiteral = 1 << 15, // Octal numeric literal
385+
Namespace = 1 << 16, // Namespace declaration
386+
ExportContext = 1 << 17, // Export context (initialized by binding)
387+
ContainsThis = 1 << 18, // Interface contains references to "this"
388+
HasImplicitReturn = 1 << 19, // If function implicitly returns on one of codepaths (initialized by binding)
389+
HasExplicitReturn = 1 << 20, // If function has explicit reachable return on one of codepaths (initialized by binding)
389390
Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async,
390391
AccessibilityModifier = Public | Private | Protected,
391-
BlockScoped = Let | Const
392+
BlockScoped = Let | Const,
393+
394+
ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn
392395
}
393396

394397
/* @internal */
@@ -2098,6 +2101,10 @@ namespace ts {
20982101
experimentalDecorators?: boolean;
20992102
emitDecoratorMetadata?: boolean;
21002103
moduleResolution?: ModuleResolutionKind;
2104+
allowUnusedLabels?: boolean;
2105+
allowUnreachableCode?: boolean;
2106+
noImplicitReturns?: boolean;
2107+
noFallthroughCasesInSwitch?: boolean;
21012108
forceConsistentCasingInFileNames?: boolean;
21022109
/* @internal */ stripInternal?: boolean;
21032110

Diff for: src/compiler/utilities.ts

+20-19
Original file line numberDiff line numberDiff line change
@@ -622,25 +622,26 @@ namespace ts {
622622
}
623623

624624
export function isFunctionLike(node: Node): node is FunctionLikeDeclaration {
625-
if (node) {
626-
switch (node.kind) {
627-
case SyntaxKind.Constructor:
628-
case SyntaxKind.FunctionExpression:
629-
case SyntaxKind.FunctionDeclaration:
630-
case SyntaxKind.ArrowFunction:
631-
case SyntaxKind.MethodDeclaration:
632-
case SyntaxKind.MethodSignature:
633-
case SyntaxKind.GetAccessor:
634-
case SyntaxKind.SetAccessor:
635-
case SyntaxKind.CallSignature:
636-
case SyntaxKind.ConstructSignature:
637-
case SyntaxKind.IndexSignature:
638-
case SyntaxKind.FunctionType:
639-
case SyntaxKind.ConstructorType:
640-
return true;
641-
}
625+
return node && isFunctionLikeKind(node.kind);
626+
}
627+
628+
export function isFunctionLikeKind(kind: SyntaxKind): boolean {
629+
switch (kind) {
630+
case SyntaxKind.Constructor:
631+
case SyntaxKind.FunctionExpression:
632+
case SyntaxKind.FunctionDeclaration:
633+
case SyntaxKind.ArrowFunction:
634+
case SyntaxKind.MethodDeclaration:
635+
case SyntaxKind.MethodSignature:
636+
case SyntaxKind.GetAccessor:
637+
case SyntaxKind.SetAccessor:
638+
case SyntaxKind.CallSignature:
639+
case SyntaxKind.ConstructSignature:
640+
case SyntaxKind.IndexSignature:
641+
case SyntaxKind.FunctionType:
642+
case SyntaxKind.ConstructorType:
643+
return true;
642644
}
643-
return false;
644645
}
645646

646647
export function introducesArgumentsExoticObject(node: Node) {
@@ -1236,7 +1237,7 @@ namespace ts {
12361237
case SyntaxKind.LabeledStatement:
12371238
case SyntaxKind.ReturnStatement:
12381239
case SyntaxKind.SwitchStatement:
1239-
case SyntaxKind.ThrowKeyword:
1240+
case SyntaxKind.ThrowStatement:
12401241
case SyntaxKind.TryStatement:
12411242
case SyntaxKind.VariableStatement:
12421243
case SyntaxKind.WhileStatement:

Diff for: src/services/breakpoints.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ namespace ts.BreakpointResolver {
446446
// fall through.
447447

448448
case SyntaxKind.CatchClause:
449-
return spanInNode(lastOrUndefined((<Block>node.parent).statements));;
449+
return spanInNode(lastOrUndefined((<Block>node.parent).statements));
450450

451451
case SyntaxKind.CaseBlock:
452452
// breakpoint in last statement of the last clause
@@ -493,9 +493,6 @@ namespace ts.BreakpointResolver {
493493
default:
494494
return spanInNode(node.parent);
495495
}
496-
497-
// Default to parent node
498-
return spanInNode(node.parent);
499496
}
500497

501498
function spanInColonToken(node: Node): TextSpan {

Diff for: src/services/formatting/smartIndenter.ts

-1
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,6 @@ namespace ts.formatting {
360360
return node;
361361
}
362362
}
363-
return node;
364363
}
365364
}
366365

0 commit comments

Comments
 (0)