Skip to content

Commit c8fc26a

Browse files
Merge pull request #147 from Microsoft/noReturnExpression
Complain when a non-void function lacks a return expresson.
2 parents 6386553 + 0f4e887 commit c8fc26a

File tree

44 files changed

+478
-93
lines changed

Some content is hidden

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

44 files changed

+478
-93
lines changed

src/compiler/checker.ts

Lines changed: 85 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3972,12 +3972,10 @@ module ts {
39723972
}
39733973

39743974
// Aggregate the types of expressions within all the return statements.
3975-
var types: Type[] = [];
3976-
checkAndAggregateReturnExpressionTypes(func.body);
3975+
var types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualType, contextualMapper);
39773976

39783977
// Try to return the best common type if we have any return expressions.
3979-
if (types.length) {
3980-
3978+
if (types.length > 0) {
39813979
var commonType = getBestCommonType(types, /*contextualType:*/ undefined, /*candidatesOnly:*/ true);
39823980
if (!commonType) {
39833981
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
@@ -4003,16 +4001,18 @@ module ts {
40034001
}
40044002

40054003
return voidType;
4004+
}
4005+
4006+
// WARNING: This has the same semantics as the forEach family of functions,
4007+
// in that traversal terminates in the event that 'visitor' supplies a truthy value.
4008+
function forEachReturnStatement<T>(body: Block, visitor: (stmt: ReturnStatement) => T): T {
4009+
4010+
return traverse(body);
40064011

4007-
function checkAndAggregateReturnExpressionTypes(node: Node) {
4012+
function traverse(node: Node): T {
40084013
switch (node.kind) {
40094014
case SyntaxKind.ReturnStatement:
4010-
var expr = (<ReturnStatement>node).expression;
4011-
if (expr) {
4012-
var type = checkAndMarkExpression(expr, contextualType, contextualMapper);
4013-
if (!contains(types, type)) types.push(type);
4014-
}
4015-
break;
4015+
return visitor(node);
40164016
case SyntaxKind.Block:
40174017
case SyntaxKind.FunctionBlock:
40184018
case SyntaxKind.IfStatement:
@@ -4029,15 +4029,77 @@ module ts {
40294029
case SyntaxKind.TryBlock:
40304030
case SyntaxKind.CatchBlock:
40314031
case SyntaxKind.FinallyBlock:
4032-
forEachChild(node, checkAndAggregateReturnExpressionTypes);
4033-
break;
4032+
return forEachChild(node, traverse);
40344033
}
40354034
}
40364035
}
40374036

4037+
/// Returns a set of types relating to every return expression relating to a function block.
4038+
function checkAndAggregateReturnExpressionTypes(body: Block, contextualType?: Type, contextualMapper?: TypeMapper): Type[] {
4039+
var aggregatedTypes: Type[] = [];
4040+
4041+
forEachReturnStatement(body, (returnStatement) => {
4042+
var expr = returnStatement.expression;
4043+
if (expr) {
4044+
var type = checkAndMarkExpression(expr, contextualType, contextualMapper);
4045+
if (!contains(aggregatedTypes, type)) {
4046+
aggregatedTypes.push(type);
4047+
}
4048+
}
4049+
});
4050+
4051+
return aggregatedTypes;
4052+
}
4053+
4054+
function bodyContainsAReturnStatement(funcBody: Block) {
4055+
return forEachReturnStatement(funcBody, (returnStatement) => {
4056+
return true;
4057+
});
4058+
}
4059+
4060+
function bodyContainsSingleThrowStatement(body: Block) {
4061+
return (body.statements.length === 1) && (body.statements[0].kind === SyntaxKind.ThrowStatement)
4062+
}
4063+
4064+
// TypeScript Specification 1.0 (6.3) - July 2014
4065+
// An explicitly typed function whose return type isn't the Void or the Any type
4066+
// must have at least one return statement somewhere in its body.
4067+
// An exception to this rule is if the function implementation consists of a single 'throw' statement.
4068+
function checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(func: FunctionDeclaration, returnType: Type): void {
4069+
// Functions that return 'void' or 'any' don't need any return expressions.
4070+
if (returnType === voidType || returnType === anyType) {
4071+
return;
4072+
}
4073+
4074+
// If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check.
4075+
if (!func.body || func.body.kind !== SyntaxKind.FunctionBlock) {
4076+
return;
4077+
}
4078+
4079+
var bodyBlock = <Block>func.body;
4080+
4081+
// Ensure the body has at least one return expression.
4082+
if (bodyContainsAReturnStatement(bodyBlock)) {
4083+
return;
4084+
}
4085+
4086+
// If there are no return expressions, then we need to check if
4087+
// the function body consists solely of a throw statement;
4088+
// this is to make an exception for unimplemented functions.
4089+
if (bodyContainsSingleThrowStatement(bodyBlock)) {
4090+
return;
4091+
}
4092+
4093+
// This function does not conform to the specification.
4094+
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);
4095+
}
4096+
40384097
function checkFunctionExpression(node: FunctionExpression, contextualType?: Type, contextualMapper?: TypeMapper): Type {
40394098
// The identityMapper object is used to indicate that function expressions are wildcards
4040-
if (contextualMapper === identityMapper) return anyFunctionType;
4099+
if (contextualMapper === identityMapper) {
4100+
return anyFunctionType;
4101+
}
4102+
40414103
var type = getTypeOfSymbol(node.symbol);
40424104
var links = getNodeLinks(node);
40434105

@@ -4055,6 +4117,9 @@ module ts {
40554117
signature.resolvedReturnType = returnType;
40564118
}
40574119
}
4120+
else {
4121+
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type));
4122+
}
40584123
}
40594124
checkSignatureDeclaration(node);
40604125
if (node.body.kind === SyntaxKind.FunctionBlock) {
@@ -4627,28 +4692,9 @@ module ts {
46274692
}
46284693

46294694
function checkAccessorDeclaration(node: AccessorDeclaration) {
4630-
function checkGetterContainsSingleThrowStatement(node: AccessorDeclaration): boolean {
4631-
var block = <Block>node.body;
4632-
return block.statements.length === 1 && block.statements[0].kind === SyntaxKind.ThrowStatement;
4633-
}
4634-
4635-
function checkGetterReturnsValue(n: Node): boolean {
4636-
switch (n.kind) {
4637-
case SyntaxKind.ReturnStatement:
4638-
return true;
4639-
// do not dive into function-like things - return statements there don't count
4640-
case SyntaxKind.FunctionExpression:
4641-
case SyntaxKind.FunctionDeclaration:
4642-
case SyntaxKind.ArrowFunction:
4643-
case SyntaxKind.ObjectLiteral:
4644-
return false;
4645-
default:
4646-
return forEachChild(n, checkGetterReturnsValue);
4647-
}
4648-
}
46494695
if (node.kind === SyntaxKind.GetAccessor) {
4650-
if (!isInAmbientContext(node) && node.body && !(checkGetterContainsSingleThrowStatement(node) || checkGetterReturnsValue(node))) {
4651-
error(node.name, Diagnostics.Getters_must_return_a_value);
4696+
if (!isInAmbientContext(node) && node.body && !(bodyContainsAReturnStatement(<Block>node.body) || bodyContainsSingleThrowStatement(<Block>node.body))) {
4697+
error(node.name, Diagnostics.A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement);
46524698
}
46534699
}
46544700

@@ -4877,8 +4923,6 @@ module ts {
48774923
}
48784924
}
48794925
}
4880-
4881-
// TODO: Check at least one return statement in non-void/any function (except single throw)
48824926
}
48834927

48844928
function checkFunctionDeclaration(node: FunctionDeclaration) {
@@ -4890,7 +4934,11 @@ module ts {
48904934
if (node === firstDeclaration) {
48914935
checkFunctionOrConstructorSymbol(symbol);
48924936
}
4937+
48934938
checkSourceElement(node.body);
4939+
if (node.type) {
4940+
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type));
4941+
}
48944942

48954943
// If there is no body and no explicit return type, then report an error.
48964944
if (program.getCompilerOptions().noImplicitAny && !node.body && !node.type) {

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@ module ts {
114114
The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter: { code: 2117, category: DiagnosticCategory.Error, key: "The right-hand side of a 'for...in' statement must be of type 'any', an object type or a type parameter." },
115115
The_left_hand_side_of_an_in_expression_must_be_of_types_any_string_or_number: { code: 2118, category: DiagnosticCategory.Error, key: "The left-hand side of an 'in' expression must be of types 'any', 'string' or 'number'." },
116116
The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter: { code: 2119, category: DiagnosticCategory.Error, key: "The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter" },
117-
Getters_must_return_a_value: { code: 2126, category: DiagnosticCategory.Error, key: "Getters must return a value." },
117+
A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement: { code: 2126, category: DiagnosticCategory.Error, key: "A 'get' accessor must return a value or consist of a single 'throw' statement." },
118118
Getter_and_setter_accessors_do_not_agree_in_visibility: { code: 2127, category: DiagnosticCategory.Error, key: "Getter and setter accessors do not agree in visibility." },
119+
A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value_or_consist_of_a_single_throw_statement: { code: 2131, 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." },
119120
Untyped_function_calls_may_not_accept_type_arguments: { code: 2158, category: DiagnosticCategory.Error, key: "Untyped function calls may not accept type arguments." },
120121
The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter: { code: 2120, category: DiagnosticCategory.Error, key: "The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter." },
121122
The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type: { code: 2121, category: DiagnosticCategory.Error, key: "The right-hand side of an 'instanceof' expression must be of type 'any' or of a type assignable to the 'Function' interface type." },

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,14 +449,18 @@
449449
"category": "Error",
450450
"code": 2119
451451
},
452-
"Getters must return a value.": {
452+
"A 'get' accessor must return a value or consist of a single 'throw' statement.": {
453453
"category": "Error",
454454
"code": 2126
455455
},
456456
"Getter and setter accessors do not agree in visibility.": {
457457
"category": "Error",
458458
"code": 2127
459459
},
460+
"A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.": {
461+
"category": "Error",
462+
"code": 2131
463+
},
460464
"Untyped function calls may not accept type arguments.": {
461465
"category": "Error",
462466
"code": 2158

src/compiler/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,7 @@ module ts {
10701070
return finishNode(node);
10711071
}
10721072

1073-
function checkIndexSignature(node: SignatureDeclaration, indexerStart: number, indexerLength: number): boolean {
1073+
function checkIndexSignature(node: SignatureDeclaration, indexerStart: number, indexerLength: number): void {
10741074
var parameter = node.parameters[0];
10751075
if (node.parameters.length !== 1) {
10761076
var arityDiagnostic = Diagnostics.An_index_signature_must_have_exactly_one_parameter

tests/baselines/reference/ParameterList5.errors.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
==== tests/cases/compiler/ParameterList5.ts (2 errors) ====
1+
==== tests/cases/compiler/ParameterList5.ts (3 errors) ====
22
function A(): (public B) => C {
3+
~~~~~~~~~~~~~~~
4+
!!! A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.
35
~~~~~~~~
46
!!! A parameter property is only allowed in a constructor implementation.
57
~

tests/baselines/reference/ambientGetters.errors.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
==== tests/cases/compiler/ambientGetters.ts (2 errors) ====
1+
==== tests/cases/compiler/ambientGetters.ts (3 errors) ====
22

33
declare class A {
44
get length() : number;
55
~
66
!!! '{' expected.
7+
~~~~~~
8+
!!! A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.
79
}
810

911
declare class B {
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
==== tests/cases/compiler/conflictingTypeAnnotatedVar.ts (2 errors) ====
1+
==== tests/cases/compiler/conflictingTypeAnnotatedVar.ts (4 errors) ====
22
var foo: string;
33
function foo(): number { }
44
~~~
55
!!! Duplicate identifier 'foo'.
6+
~~~~~~
7+
!!! A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.
68
function foo(): number { }
79
~~~
8-
!!! Duplicate identifier 'foo'.
10+
!!! Duplicate identifier 'foo'.
11+
~~~~~~
12+
!!! A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
==== tests/cases/compiler/errorOnContextuallyTypedReturnType.ts (1 errors) ====
1+
==== tests/cases/compiler/errorOnContextuallyTypedReturnType.ts (2 errors) ====
22
var n1: () => boolean = function () { }; // expect an error here
33
~~
44
!!! Type '() => void' is not assignable to type '() => boolean':
55
!!! Type 'void' is not assignable to type 'boolean'.
66
var n2: () => boolean = function ():boolean { }; // expect an error here
7+
~~~~~~~
8+
!!! A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.
79

tests/baselines/reference/functionImplementationErrors.errors.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
==== tests/cases/conformance/functions/functionImplementationErrors.ts (6 errors) ====
1+
==== tests/cases/conformance/functions/functionImplementationErrors.ts (7 errors) ====
22
// FunctionExpression with no return type annotation with multiple return statements with unrelated types
33
var f1 = function () {
44
~~~~~~~~~~~~~
@@ -47,6 +47,8 @@
4747

4848
// Function implemetnation with non -void return type annotation with no return
4949
function f5(): number {
50+
~~~~~~
51+
!!! A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.
5052
}
5153

5254
var m;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
==== tests/cases/compiler/functionWithThrowButNoReturn1.ts (1 errors) ====
2+
function fn(): number {
3+
~~~~~~
4+
!!! A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement.
5+
throw new Error('NYI');
6+
var t;
7+
}
8+

0 commit comments

Comments
 (0)