diff --git a/Jakefile b/Jakefile
index ba82e9c3f4363..815bc84cd4c86 100644
--- a/Jakefile
+++ b/Jakefile
@@ -252,7 +252,7 @@ compileFile(tscFile, compilerSources, [builtLocalDirectory, copyright].concat(co
var servicesFile = path.join(builtLocalDirectory, "typescriptServices.js");
var servicesDefinitionsFile = path.join(builtLocalDirectory, "typescriptServices.d.ts");
-compileFile(servicesFile, servicesSources, [builtLocalDirectory, copyright].concat(servicesSources), [copyright], /*useBuiltCompiler:*/ true, /*noOutFile:*/ false, /*generateDeclarations:*/ true);
+compileFile(servicesFile, servicesSources, [builtLocalDirectory, copyright].concat(servicesSources), [copyright], /*useBuiltCompiler:*/ false, /*noOutFile:*/ false, /*generateDeclarations:*/ true);
// Local target to build the compiler and services
desc("Builds the full compiler and services");
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index c43d1074dfa20..9c243a91e3803 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -4,6 +4,7 @@
///
///
///
+///
module ts {
var nextSymbolId = 1;
@@ -6150,7 +6151,7 @@ module ts {
function checkFunctionExpressionBody(node: FunctionExpression) {
if (node.type) {
- checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type));
+ checkControlFlowOfFunction(node, getTypeFromTypeNode(node.type) !== voidType, error);
}
if (node.body.kind === SyntaxKind.FunctionBlock) {
checkSourceElement(node.body);
@@ -7294,7 +7295,7 @@ module ts {
checkSourceElement(node.body);
if (node.type && !isAccessor(node.kind)) {
- checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type));
+ checkControlFlowOfFunction(node, getTypeFromTypeNode(node.type) !== voidType, error);
}
// If there is no body and no explicit return type, then report an error.
diff --git a/src/compiler/controlflow.ts b/src/compiler/controlflow.ts
new file mode 100644
index 0000000000000..78c30dce27ed5
--- /dev/null
+++ b/src/compiler/controlflow.ts
@@ -0,0 +1,382 @@
+///
+
+module ts {
+ export function checkControlFlowOfFunction(decl: FunctionLikeDeclaration, noImplicitReturns: boolean, error: (n: Node, message: DiagnosticMessage, arg0?: any) => void) {
+ if (!decl.body || decl.body.kind !== SyntaxKind.FunctionBlock) {
+ return;
+ }
+
+ var finalState = checkControlFlow(decl.body, error);
+ if (noImplicitReturns && finalState === ControlFlowState.Reachable) {
+ var errorNode: Node = decl.name || decl;
+ error(errorNode, Diagnostics.Not_all_code_paths_return_a_value);
+ }
+ }
+
+ export function checkControlFlowOfBlock(block: Block, error: (n: Node, message: DiagnosticMessage, arg0?: any) => void) {
+ checkControlFlow(block, error);
+ }
+
+ function checkControlFlow(decl: Node, error: (n: Node, message: DiagnosticMessage, arg0?: any) => void): ControlFlowState {
+ var currentState = ControlFlowState.Reachable;
+
+ function setState(newState: ControlFlowState) {
+ currentState = newState;
+ }
+
+ function or(s1: ControlFlowState, s2: ControlFlowState): ControlFlowState {
+ if (s1 === ControlFlowState.Reachable || s2 === ControlFlowState.Reachable) {
+ return ControlFlowState.Reachable;
+ }
+ if (s1 === ControlFlowState.ReportedUnreachable && s2 === ControlFlowState.ReportedUnreachable) {
+ return ControlFlowState.ReportedUnreachable;
+ }
+ return ControlFlowState.Unreachable;
+ }
+
+ function reportIfNotReachable(n: Node): boolean {
+ switch (currentState) {
+ case ControlFlowState.Unreachable:
+ error(n, Diagnostics.Unreachable_code_detected);
+ currentState = ControlFlowState.ReportedUnreachable;
+ return true;
+ case ControlFlowState.ReportedUnreachable:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // label name -> index in 'labelStack'
+ var labels: Map = {};
+ // CF state at all seen labels
+ var labelStack: ControlFlowState[] = [];
+ // indices of implicit labels in 'labelStack'
+ var implicitLabels: number[] = [];
+
+ function pushNamedLabel(name: Identifier): boolean {
+ if (hasProperty(labels, name.text)) {
+ return false;
+ }
+ var newLen = labelStack.push(ControlFlowState.Uninitialized);
+ labels[name.text] = newLen - 1;
+ return true;
+ }
+
+ function pushImplicitLabel(): number {
+ var newLen = labelStack.push(ControlFlowState.Uninitialized);
+ implicitLabels.push(newLen - 1);
+ return newLen - 1;
+ }
+
+ function setFinalStateAtLabel(mergedStates: ControlFlowState, outerState: ControlFlowState, name: Identifier): void {
+ if (mergedStates === ControlFlowState.Uninitialized) {
+ if (name) {
+ error(name, Diagnostics.Unused_label);
+ }
+ setState(outerState);
+ }
+ else {
+ setState(or(mergedStates, outerState));
+ }
+ }
+
+ function popNamedLabel(name: Identifier, outerState: ControlFlowState): void {
+ Debug.assert(hasProperty(labels, name.text));
+ var index = labels[name.text];
+ Debug.assert(labelStack.length === index + 1);
+ labels[name.text] = undefined;
+ var mergedStates = labelStack.pop();
+ setFinalStateAtLabel(mergedStates, outerState, name);
+ }
+
+ function popImplicitLabel(index: number, outerState: ControlFlowState): void {
+ Debug.assert(labelStack.length === index + 1);
+ var i = implicitLabels.pop();
+ Debug.assert(index === i);
+ var mergedStates = labelStack.pop();
+ setFinalStateAtLabel(mergedStates, outerState, /*name*/ undefined);
+ }
+
+ function gotoLabel(label: Identifier, outerState: ControlFlowState): void {
+ var stateIndex: number;
+ if (label) {
+ if (!hasProperty(labels, label.text)) {
+ // reference to non-existing label
+ return;
+ }
+ stateIndex = labels[label.text];
+ }
+ else {
+ if (implicitLabels.length === 0) {
+ // non-labeled break\continue being used outside loops
+ return;
+ }
+
+ stateIndex = implicitLabels[implicitLabels.length - 1];
+ }
+ var stateAtLabel = labelStack[stateIndex];
+ labelStack[stateIndex] = stateAtLabel === ControlFlowState.Uninitialized ? outerState : or(outerState, stateAtLabel);
+ }
+
+ function checkWhileStatement(n: WhileStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+
+ var preWhileState =
+ n.expression.kind === SyntaxKind.FalseKeyword ? ControlFlowState.Unreachable : currentState;
+ var postWhileState =
+ n.expression.kind === SyntaxKind.TrueKeyword ? ControlFlowState.Unreachable : currentState;
+
+ setState(preWhileState);
+
+ var index = pushImplicitLabel();
+ check(n.statement);
+ popImplicitLabel(index, postWhileState);
+ }
+
+ function checkDoStatement(n: DoStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+
+ var preDoState = currentState;
+
+ var index = pushImplicitLabel();
+ check(n.statement);
+
+ var postDoState = n.expression.kind === SyntaxKind.TrueKeyword ? ControlFlowState.Unreachable : preDoState;
+ popImplicitLabel(index, postDoState);
+ }
+
+ function checkForStatement(n: ForStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+
+ var preForState = currentState;
+ var index = pushImplicitLabel();
+ check(n.statement);
+
+ // for statement is considered infinite when it condition is either omitted or is true keyword
+ // - for(..;;..)
+ // - for(..;true;..)
+ var isInfiniteLoop = (!n.condition || n.condition.kind === SyntaxKind.TrueKeyword);
+
+ var postForState = isInfiniteLoop ? ControlFlowState.Unreachable : preForState;
+ popImplicitLabel(index, postForState);
+ }
+
+ function checkForInStatement(n: ForInStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+
+ var preForInState = currentState;
+ var index = pushImplicitLabel();
+ check(n.statement);
+ popImplicitLabel(index, preForInState);
+ }
+
+ function checkBlock(n: Block): void {
+ forEach(n.statements, check);
+ }
+
+ function checkIfStatement(n: IfStatement): void {
+ var ifTrueState: ControlFlowState = n.expression.kind === SyntaxKind.FalseKeyword ? ControlFlowState.Unreachable : currentState;
+ var ifFalseState: ControlFlowState = n.expression.kind === SyntaxKind.TrueKeyword ? ControlFlowState.Unreachable : currentState;
+
+ setState(ifTrueState);
+ check(n.thenStatement);
+ if (n.elseStatement) {
+ ifTrueState = currentState;
+ setState(ifFalseState);
+ check(n.elseStatement);
+ setState(or(currentState, ifTrueState));
+ }
+ else {
+ setState(or(currentState, ifFalseState))
+ }
+ }
+
+ function checkReturnOrThrow(n: Node): void {
+ reportIfNotReachable(n);
+ setState(ControlFlowState.Unreachable);
+ }
+
+ function checkBreakOrContinueStatement(n: BreakOrContinueStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+ if (n.kind === SyntaxKind.BreakStatement) {
+ gotoLabel(n.label, currentState);
+ }
+ else {
+ gotoLabel(n.label, ControlFlowState.Unreachable); // touch label so it will be marked a used
+ }
+ setState(ControlFlowState.Unreachable);
+ }
+
+ function checkTryStatement(n: TryStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+
+ // catch\finally blocks has the same reachability as try block
+ var startState = currentState;
+ check(n.tryBlock);
+ var postTryState = currentState;
+
+ setState(startState);
+ check(n.catchBlock);
+ var postCatchState = currentState;
+
+ if (n.finallyBlock) {
+ setState(startState);
+ check(n.finallyBlock);
+ // post-finally state become current state
+ }
+ else {
+ // post catch state is reachable if
+ // - post try state is reachable - control flow can fall out of try block
+ // - post catch state is reachable - control flow can fall out of catch block
+ setState(or(postTryState, postCatchState))
+ }
+ }
+
+ function checkSwitchStatement(n: SwitchStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+
+ var startState = currentState;
+ var hasDefault = false;
+
+ var index = pushImplicitLabel();
+
+ forEach(n.clauses, (c: CaseOrDefaultClause) => {
+ hasDefault = hasDefault || c.kind === SyntaxKind.DefaultClause;
+ setState(startState);
+ forEach(c.statements, check);
+ if (c.statements.length && currentState === ControlFlowState.Reachable) {
+ error(c.expression, Diagnostics.Fallthrough_case_in_switch);
+ }
+ });
+
+ // post switch state is unreachable if switch is exaustive (has a default case ) and does not have fallthrough from the last case
+ var postSwitchState = hasDefault && currentState !== ControlFlowState.Reachable ? ControlFlowState.Unreachable : startState;
+
+ popImplicitLabel(index, postSwitchState);
+ }
+
+ function checkLabelledStatement(n: LabeledStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+
+ var ok = pushNamedLabel(n.label);
+ check(n.statement);
+ if (ok) {
+ popNamedLabel(n.label, currentState);
+ }
+ }
+
+ function checkWithStatement(n: WithStatement): void {
+ if (reportIfNotReachable(n)) {
+ // current state is unreachable.
+ // since nothing downstream can change it - no need to continue
+ return;
+ }
+
+ check(n.statement);
+ }
+
+ // current assumption: only statements affect CF
+ function check(n: Node): void {
+ if (!n || currentState === ControlFlowState.ReportedUnreachable) {
+ return;
+ }
+ switch (n.kind) {
+ case SyntaxKind.WhileStatement:
+ checkWhileStatement(n);
+ break;
+ case SyntaxKind.SourceFile:
+ forEach((n).statements, check);
+ break;
+ case SyntaxKind.Block:
+ case SyntaxKind.TryBlock:
+ case SyntaxKind.CatchBlock:
+ case SyntaxKind.FinallyBlock:
+ case SyntaxKind.ModuleBlock:
+ case SyntaxKind.FunctionBlock:
+ checkBlock(n);
+ break;
+ case SyntaxKind.IfStatement:
+ checkIfStatement(n);
+ break;
+ case SyntaxKind.ReturnStatement:
+ case SyntaxKind.ThrowStatement:
+ checkReturnOrThrow(n);
+ break;
+ case SyntaxKind.BreakStatement:
+ case SyntaxKind.ContinueStatement:
+ checkBreakOrContinueStatement(n);
+ break;
+ case SyntaxKind.VariableStatement:
+ case SyntaxKind.EmptyStatement:
+ case SyntaxKind.ExpressionStatement:
+ case SyntaxKind.DebuggerStatement:
+ reportIfNotReachable(n);
+ break;
+ case SyntaxKind.DoStatement:
+ checkDoStatement(n);
+ break;
+ case SyntaxKind.ForInStatement:
+ checkForInStatement(n);
+ break;
+ case SyntaxKind.ForStatement:
+ checkForStatement(n);
+ break;
+ case SyntaxKind.LabeledStatement:
+ checkLabelledStatement(n);
+ break;
+ case SyntaxKind.SwitchStatement:
+ checkSwitchStatement(n);
+ break;
+ case SyntaxKind.TryStatement:
+ checkTryStatement(n);
+ break;
+ case SyntaxKind.WithStatement:
+ checkWithStatement(n);
+ break;
+ }
+ }
+
+ check(decl);
+ return currentState;
+ }
+
+ const enum ControlFlowState {
+ Uninitialized = 0,
+ Reachable = 1,
+ Unreachable = 2,
+ ReportedUnreachable = 3,
+ }
+}
\ No newline at end of file
diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts
index c17cf94e8c007..88143272ab658 100644
--- a/src/compiler/diagnosticInformationMap.generated.ts
+++ b/src/compiler/diagnosticInformationMap.generated.ts
@@ -288,6 +288,10 @@ module ts {
Type_alias_0_circularly_references_itself: { code: 2456, category: DiagnosticCategory.Error, key: "Type alias '{0}' circularly references itself." },
Type_alias_name_cannot_be_0: { code: 2457, category: DiagnosticCategory.Error, key: "Type alias name cannot be '{0}'" },
An_AMD_module_cannot_have_multiple_name_assignments: { code: 2458, category: DiagnosticCategory.Error, key: "An AMD module cannot have multiple name assignments." },
+ Not_all_code_paths_return_a_value: { code: 2459, category: DiagnosticCategory.Warning, key: "Not all code paths return a value" },
+ Unreachable_code_detected: { code: 2460, category: DiagnosticCategory.Warning, key: "Unreachable code detected" },
+ Unused_label: { code: 2461, category: DiagnosticCategory.Warning, key: "Unused label" },
+ Fallthrough_case_in_switch: { code: 2462, category: DiagnosticCategory.Warning, key: "Fallthrough case in switch" },
Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." },
Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." },
Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." },
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index 3d5be6d566a89..8ceedac516662 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -1147,8 +1147,23 @@
"An AMD module cannot have multiple name assignments.": {
"category": "Error",
"code": 2458
+ },
+ "Not all code paths return a value": {
+ "category": "Warning",
+ "code": 2459
+ },
+ "Unreachable code detected": {
+ "category": "Warning",
+ "code": 2460
+ },
+ "Unused label": {
+ "category": "Warning",
+ "code": 2461
+ },
+ "Fallthrough case in switch": {
+ "category": "Warning",
+ "code": 2462
},
-
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
"code": 4000