Skip to content

Commit b62ef0d

Browse files
committed
initial revision of reachability checks in binder
1 parent 690b87c commit b62ef0d

75 files changed

Lines changed: 1570 additions & 204 deletions

File tree

Some content is hidden

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

src/compiler/binder.ts

Lines changed: 412 additions & 18 deletions
Large diffs are not rendered by default.

src/compiler/checker.ts

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ namespace ts {
158158
let getGlobalPromiseConstructorLikeType: () => ObjectType;
159159
let getGlobalThenableType: () => ObjectType;
160160

161+
let jsxElementClassType: Type;
162+
161163
let tupleTypes: Map<TupleType> = {};
162164
let unionTypes: Map<UnionType> = {};
163165
let intersectionTypes: Map<IntersectionType> = {};
@@ -7560,7 +7562,6 @@ namespace ts {
75607562
return prop || unknownSymbol;
75617563
}
75627564

7563-
let jsxElementClassType: Type = undefined;
75647565
function getJsxGlobalElementClassType(): Type {
75657566
if (!jsxElementClassType) {
75667567
jsxElementClassType = getExportedTypeFromNamespace(JsxNames.JSX, JsxNames.ElementClass);
@@ -9277,21 +9278,11 @@ namespace ts {
92779278
return aggregatedTypes;
92789279
}
92799280

9280-
function bodyContainsAReturnStatement(funcBody: Block) {
9281-
return forEachReturnStatement(funcBody, returnStatement => {
9282-
return true;
9283-
});
9284-
}
9285-
9286-
function bodyContainsSingleThrowStatement(body: Block) {
9287-
return (body.statements.length === 1) && (body.statements[0].kind === SyntaxKind.ThrowStatement);
9288-
}
9289-
92909281
// TypeScript Specification 1.0 (6.3) - July 2014
92919282
// An explicitly typed function whose return type isn't the Void or the Any type
92929283
// must have at least one return statement somewhere in its body.
92939284
// An exception to this rule is if the function implementation consists of a single 'throw' statement.
9294-
function checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(func: FunctionLikeDeclaration, returnType: Type): void {
9285+
function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration, returnType: Type): void {
92959286
if (!produceDiagnostics) {
92969287
return;
92979288
}
@@ -9302,26 +9293,20 @@ namespace ts {
93029293
}
93039294

93049295
// If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check.
9305-
if (nodeIsMissing(func.body) || func.body.kind !== SyntaxKind.Block) {
9296+
// also if HasImplicitReturnValue flags is not set this means that all codepaths in function body end with return of throw
9297+
if (nodeIsMissing(func.body) || func.body.kind !== SyntaxKind.Block || !(func.flags & NodeFlags.HasImplicitReturn)) {
93069298
return;
93079299
}
9308-
9309-
let bodyBlock = <Block>func.body;
9310-
9311-
// Ensure the body has at least one return expression.
9312-
if (bodyContainsAReturnStatement(bodyBlock)) {
9313-
return;
9300+
9301+
if (func.flags & NodeFlags.HasExplicitReturn) {
9302+
if (compilerOptions.noImplicitReturns) {
9303+
error(func.type, Diagnostics.Not_all_code_paths_return_a_value);
9304+
}
93149305
}
9315-
9316-
// If there are no return expressions, then we need to check if
9317-
// the function body consists solely of a throw statement;
9318-
// this is to make an exception for unimplemented functions.
9319-
if (bodyContainsSingleThrowStatement(bodyBlock)) {
9320-
return;
9306+
else {
9307+
// This function does not conform to the specification.
9308+
error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
93219309
}
9322-
9323-
// This function does not conform to the specification.
9324-
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);
93259310
}
93269311

93279312
function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | MethodDeclaration, contextualMapper?: TypeMapper): Type {
@@ -9401,7 +9386,7 @@ namespace ts {
94019386
}
94029387

94039388
if (returnType && !node.asteriskToken) {
9404-
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, isAsync ? promisedType : returnType);
9389+
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, isAsync ? promisedType : returnType);
94059390
}
94069391

94079392
if (node.body) {
@@ -10579,8 +10564,15 @@ namespace ts {
1057910564
checkGrammarFunctionLikeDeclaration(node) || checkGrammarAccessor(node) || checkGrammarComputedPropertyName(node.name);
1058010565

1058110566
if (node.kind === SyntaxKind.GetAccessor) {
10582-
if (!isInAmbientContext(node) && nodeIsPresent(node.body) && !(bodyContainsAReturnStatement(<Block>node.body) || bodyContainsSingleThrowStatement(<Block>node.body))) {
10583-
error(node.name, Diagnostics.A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement);
10567+
if (!isInAmbientContext(node) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) {
10568+
if (node.flags & NodeFlags.HasExplicitReturn) {
10569+
if (compilerOptions.noImplicitReturns) {
10570+
error(node.name, Diagnostics.Not_all_code_paths_return_a_value);
10571+
}
10572+
}
10573+
else {
10574+
error(node.name, Diagnostics.A_get_accessor_must_return_a_value);
10575+
}
1058410576
}
1058510577
}
1058610578

@@ -11505,7 +11497,7 @@ namespace ts {
1150511497
promisedType = checkAsyncFunctionReturnType(node);
1150611498
}
1150711499

11508-
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, isAsync ? promisedType : returnType);
11500+
checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, isAsync ? promisedType : returnType);
1150911501
}
1151011502

1151111503
if (produceDiagnostics && !node.type) {
@@ -14524,7 +14516,7 @@ namespace ts {
1452414516
function initializeTypeChecker() {
1452514517
// Bind all source files and propagate errors
1452614518
forEach(host.getSourceFiles(), file => {
14527-
bindSourceFile(file);
14519+
bindSourceFile(file, compilerOptions);
1452814520
});
1452914521

1453014522
// Initialize global symbol table

src/compiler/commandLineParser.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,27 @@ namespace ts {
244244
"classic": ModuleResolutionKind.Classic
245245
},
246246
description: Diagnostics.Specifies_module_resolution_strategy_Colon_node_Node_or_classic_TypeScript_pre_1_6
247-
}
247+
},
248+
{
249+
name: "noUnusedLabels",
250+
type: "boolean",
251+
description: Diagnostics.Report_error_on_unused_labels
252+
},
253+
{
254+
name: "noImplicitReturns",
255+
type: "boolean",
256+
description: Diagnostics.Report_error_when_not_all_code_paths_in_function_return_a_value
257+
},
258+
{
259+
name: "noFallthroughCasesInSwitch",
260+
type: "boolean",
261+
description: Diagnostics.Report_errors_for_fallthrough_cases_in_switch_statement
262+
},
263+
{
264+
name: "noUnreachableCode",
265+
type: "boolean",
266+
description: Diagnostics.Report_errors_on_unreachable_code
267+
}
248268
];
249269

250270
/* @internal */

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ namespace ts {
256256
Neither_type_0_nor_type_1_is_assignable_to_the_other: { code: 2352, category: DiagnosticCategory.Error, key: "Neither type '{0}' nor type '{1}' is assignable to the other." },
257257
Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1: { code: 2353, category: DiagnosticCategory.Error, key: "Object literal may only specify known properties, and '{0}' does not exist in type '{1}'." },
258258
No_best_common_type_exists_among_return_expressions: { code: 2354, category: DiagnosticCategory.Error, key: "No best common type exists among return expressions." },
259-
A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value_or_consist_of_a_single_throw_statement: { code: 2355, category: DiagnosticCategory.Error, key: "A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement." },
259+
A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value: { code: 2355, category: DiagnosticCategory.Error, key: "A function whose declared type is neither 'void' nor 'any' must return a value." },
260260
An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type: { code: 2356, category: DiagnosticCategory.Error, key: "An arithmetic operand must be of type 'any', 'number' or an enum type." },
261261
The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_property_or_indexer: { code: 2357, category: DiagnosticCategory.Error, key: "The operand of an increment or decrement operator must be a variable, property or indexer." },
262262
The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter: { code: 2358, category: DiagnosticCategory.Error, key: "The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter." },
@@ -277,7 +277,7 @@ namespace ts {
277277
Duplicate_number_index_signature: { code: 2375, category: DiagnosticCategory.Error, key: "Duplicate number index signature." },
278278
A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_or_has_parameter_properties: { code: 2376, category: DiagnosticCategory.Error, key: "A 'super' call must be the first statement in the constructor when a class contains initialized properties or has parameter properties." },
279279
Constructors_for_derived_classes_must_contain_a_super_call: { code: 2377, category: DiagnosticCategory.Error, key: "Constructors for derived classes must contain a 'super' call." },
280-
A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement: { code: 2378, category: DiagnosticCategory.Error, key: "A 'get' accessor must return a value or consist of a single 'throw' statement." },
280+
A_get_accessor_must_return_a_value: { code: 2378, category: DiagnosticCategory.Error, key: "A 'get' accessor must return a value." },
281281
Getter_and_setter_accessors_do_not_agree_in_visibility: { code: 2379, category: DiagnosticCategory.Error, key: "Getter and setter accessors do not agree in visibility." },
282282
get_and_set_accessor_must_have_the_same_type: { code: 2380, category: DiagnosticCategory.Error, key: "'get' and 'set' accessor must have the same type." },
283283
A_signature_with_an_implementation_cannot_use_a_string_literal_type: { code: 2381, category: DiagnosticCategory.Error, key: "A signature with an implementation cannot use a string literal type." },
@@ -570,6 +570,10 @@ namespace ts {
570570
Initializes_a_TypeScript_project_and_creates_a_tsconfig_json_file: { code: 6070, category: DiagnosticCategory.Message, key: "Initializes a TypeScript project and creates a tsconfig.json file." },
571571
Successfully_created_a_tsconfig_json_file: { code: 6071, category: DiagnosticCategory.Message, key: "Successfully created a tsconfig.json file." },
572572
Suppress_excess_property_checks_for_object_literals: { code: 6072, category: DiagnosticCategory.Message, key: "Suppress excess property checks for object literals." },
573+
Report_error_on_unused_labels: { code: 6073, category: DiagnosticCategory.Message, key: "Report error on unused labels." },
574+
Report_error_when_not_all_code_paths_in_function_return_a_value: { code: 6074, category: DiagnosticCategory.Message, key: "Report error when not all code paths in function return a value." },
575+
Report_errors_for_fallthrough_cases_in_switch_statement: { code: 6075, category: DiagnosticCategory.Message, key: "Report errors for fallthrough cases in switch statement." },
576+
Report_errors_on_unreachable_code: { code: 6076, category: DiagnosticCategory.Message, key: "Report errors on unreachable code." },
573577
Variable_0_implicitly_has_an_1_type: { code: 7005, category: DiagnosticCategory.Error, key: "Variable '{0}' implicitly has an '{1}' type." },
574578
Parameter_0_implicitly_has_an_1_type: { code: 7006, category: DiagnosticCategory.Error, key: "Parameter '{0}' implicitly has an '{1}' type." },
575579
Member_0_implicitly_has_an_1_type: { code: 7008, category: DiagnosticCategory.Error, key: "Member '{0}' implicitly has an '{1}' type." },
@@ -587,6 +591,10 @@ namespace ts {
587591
Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions: { code: 7024, category: DiagnosticCategory.Error, key: "Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions." },
588592
Generator_implicitly_has_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type: { code: 7025, category: DiagnosticCategory.Error, key: "Generator implicitly has type '{0}' because it does not yield any values. Consider supplying a return type." },
589593
JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists: { code: 7026, category: DiagnosticCategory.Error, key: "JSX element implicitly has type 'any' because no interface 'JSX.{0}' exists" },
594+
Unreachable_code_detected: { code: 7027, category: DiagnosticCategory.Error, key: "Unreachable code detected." },
595+
Unused_label: { code: 7028, category: DiagnosticCategory.Error, key: "Unused label." },
596+
Fallthrough_case_in_switch: { code: 7029, category: DiagnosticCategory.Error, key: "Fallthrough case in switch." },
597+
Not_all_code_paths_return_a_value: { code: 7030, category: DiagnosticCategory.Error, key: "Not all code paths return a value." },
590598
You_cannot_rename_this_element: { code: 8000, category: DiagnosticCategory.Error, key: "You cannot rename this element." },
591599
You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library: { code: 8001, category: DiagnosticCategory.Error, key: "You cannot rename elements that are defined in the standard TypeScript library." },
592600
import_can_only_be_used_in_a_ts_file: { code: 8002, category: DiagnosticCategory.Error, key: "'import ... =' can only be used in a .ts file." },

src/compiler/diagnosticMessages.json

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,7 +1013,7 @@
10131013
"category": "Error",
10141014
"code": 2354
10151015
},
1016-
"A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.": {
1016+
"A function whose declared type is neither 'void' nor 'any' must return a value.": {
10171017
"category": "Error",
10181018
"code": 2355
10191019
},
@@ -1097,7 +1097,7 @@
10971097
"category": "Error",
10981098
"code": 2377
10991099
},
1100-
"A 'get' accessor must return a value or consist of a single 'throw' statement.": {
1100+
"A 'get' accessor must return a value.": {
11011101
"category": "Error",
11021102
"code": 2378
11031103
},
@@ -2270,7 +2270,22 @@
22702270
"category": "Message",
22712271
"code": 6072
22722272
},
2273-
2273+
"Report error on unused labels.": {
2274+
"category": "Message",
2275+
"code": 6073
2276+
},
2277+
"Report error when not all code paths in function return a value.": {
2278+
"category": "Message",
2279+
"code": 6074
2280+
},
2281+
"Report errors for fallthrough cases in switch statement.": {
2282+
"category": "Message",
2283+
"code": 6075
2284+
},
2285+
"Report errors on unreachable code.": {
2286+
"category": "Message",
2287+
"code": 6076
2288+
},
22742289
"Variable '{0}' implicitly has an '{1}' type.": {
22752290
"category": "Error",
22762291
"code": 7005
@@ -2339,8 +2354,22 @@
23392354
"category": "Error",
23402355
"code": 7026
23412356
},
2342-
2343-
2357+
"Unreachable code detected.": {
2358+
"category": "Error",
2359+
"code": 7027
2360+
},
2361+
"Unused label.": {
2362+
"category": "Error",
2363+
"code": 7028
2364+
},
2365+
"Fallthrough case in switch.": {
2366+
"category": "Error",
2367+
"code": 7029
2368+
},
2369+
"Not all code paths return a value.": {
2370+
"category": "Error",
2371+
"code": 7030
2372+
},
23442373
"You cannot rename this element.": {
23452374
"category": "Error",
23462375
"code": 8000

src/compiler/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,8 @@ namespace ts {
376376
OctalLiteral = 0x00010000, // Octal numeric literal
377377
Namespace = 0x00020000, // Namespace declaration
378378
ExportContext = 0x00040000, // Export context (initialized by binding)
379+
HasImplicitReturn = 0x00080000, // If function implicitly returns on one of codepaths (initialized by binding)
380+
HasExplicitReturn = 0x00100000, // If function has explicit reachable return on one of codepaths (initialized by binding)
379381

380382
Modifier = Export | Ambient | Public | Private | Protected | Static | Abstract | Default | Async,
381383
AccessibilityModifier = Public | Private | Protected,
@@ -2057,7 +2059,11 @@ namespace ts {
20572059
experimentalDecorators?: boolean;
20582060
experimentalAsyncFunctions?: boolean;
20592061
emitDecoratorMetadata?: boolean;
2060-
moduleResolution?: ModuleResolutionKind
2062+
moduleResolution?: ModuleResolutionKind,
2063+
noUnusedLabels?: boolean,
2064+
noImplicitReturns?: boolean,
2065+
noFallthroughCasesInSwitch?: boolean,
2066+
noUnreachableCode?: boolean,
20612067
/* @internal */ stripInternal?: boolean;
20622068

20632069
// Skip checking lib.d.ts to help speed up tests.

src/harness/harness.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,18 @@ module Harness {
12421242
setting.value.toLowerCase() === "preserve" ? ts.JsxEmit.Preserve :
12431243
ts.JsxEmit.None;
12441244
break;
1245+
case "nounusedlabels":
1246+
options.noUnusedLabels = setting.value === "true"
1247+
break;
1248+
case "noimplicitreturns":
1249+
options.noImplicitReturns = setting.value === "true"
1250+
break;
1251+
case "nofallthroughcasesinswitch":
1252+
options.noFallthroughCasesInSwitch = setting.value === "true"
1253+
break;
1254+
case "nounreachablecode":
1255+
options.noUnreachableCode = setting.value === "true"
1256+
break;
12451257

12461258
default:
12471259
throw new Error("Unsupported compiler setting " + setting.flag);

src/services/breakpoints.ts

Lines changed: 1 addition & 4 deletions
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 {

src/services/formatting/smartIndenter.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,6 @@ namespace ts.formatting {
340340
return node;
341341
}
342342
}
343-
return node;
344343
}
345344
}
346345

0 commit comments

Comments
 (0)