Skip to content

Commit d33127a

Browse files
Complain when a non-void/any function lacks a return expresson.
In effect this fixes #62. Also - Changes the error message for get accessors lacking return expressions. - Actually checks for return expressions instead of return statements for get-accessors. - Removes fancy quotes. - Corrects errors in the compiler caught by the new check. - Simplified `checkAndAggregateReturnTypes` by extracting it out to `visitReturnStatements`.
1 parent 42f6a99 commit d33127a

File tree

45 files changed

+379
-94
lines changed

Some content is hidden

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

45 files changed

+379
-94
lines changed

src/compiler/checker.ts

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3932,12 +3932,10 @@ module ts {
39323932
}
39333933

39343934
// Aggregate the types of expressions within all the return statements.
3935-
var types: Type[] = [];
3936-
checkAndAggregateReturnExpressionTypes(func.body);
3935+
var types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualType, contextualMapper);
39373936

39383937
// Try to return the best common type if we have any return expressions.
3939-
if (types.length) {
3940-
3938+
if (types.length > 0) {
39413939
var commonType = getBestCommonType(types, /*contextualType:*/ undefined, /*candidatesOnly:*/ true);
39423940
if (!commonType) {
39433941
error(func, Diagnostics.No_best_common_type_exists_among_return_expressions);
@@ -3963,16 +3961,18 @@ module ts {
39633961
}
39643962

39653963
return voidType;
3964+
}
3965+
3966+
// WARNING: This has the same semantics as forEach, in that traversal terminates
3967+
// in the event that 'visitor' supplies a truthy value!
3968+
function visitReturnStatements<T>(body: Block, visitor: (stmt: ReturnStatement) => T): T {
3969+
3970+
return visitHelper(body);
39663971

3967-
function checkAndAggregateReturnExpressionTypes(node: Node) {
3972+
function visitHelper(node: Node): T {
39683973
switch (node.kind) {
39693974
case SyntaxKind.ReturnStatement:
3970-
var expr = (<ReturnStatement>node).expression;
3971-
if (expr) {
3972-
var type = checkAndMarkExpression(expr, contextualType, contextualMapper);
3973-
if (!contains(types, type)) types.push(type);
3974-
}
3975-
break;
3975+
return visitor(node);
39763976
case SyntaxKind.Block:
39773977
case SyntaxKind.FunctionBlock:
39783978
case SyntaxKind.IfStatement:
@@ -3989,15 +3989,77 @@ module ts {
39893989
case SyntaxKind.TryBlock:
39903990
case SyntaxKind.CatchBlock:
39913991
case SyntaxKind.FinallyBlock:
3992-
forEachChild(node, checkAndAggregateReturnExpressionTypes);
3993-
break;
3992+
return forEachChild(node, visitHelper);
3993+
}
3994+
}
3995+
}
3996+
3997+
/// Returns a set of types relating to every return expression relating to a function block.
3998+
function checkAndAggregateReturnExpressionTypes(body: Block, contextualType?: Type, contextualMapper?: TypeMapper): Type[] {
3999+
var aggregatedTypes: Type[] = [];
4000+
4001+
visitReturnStatements(body, (returnStatement) => {
4002+
var expr = returnStatement.expression;
4003+
if (expr) {
4004+
var type = checkAndMarkExpression(expr, contextualType, contextualMapper);
4005+
if (!contains(aggregatedTypes, type)) {
4006+
aggregatedTypes.push(type);
4007+
}
39944008
}
4009+
});
4010+
4011+
return aggregatedTypes;
4012+
}
4013+
4014+
function bodyContainsReturnExpressions(funcBody: Block) {
4015+
return visitReturnStatements(funcBody, (returnStatement) => {
4016+
return returnStatement.expression !== undefined;
4017+
});
4018+
}
4019+
4020+
function bodyContainsSingleThrowStatement(body: Block) {
4021+
return (body.statements.length === 1) && (body.statements[0].kind === SyntaxKind.ThrowStatement)
4022+
}
4023+
4024+
// TypeScript Specification 1.0 (6.3) - July 2014
4025+
// An explicitly typed function whose return type isn't the Void or the Any type
4026+
// must have at least one return statement somewhere in its body.
4027+
// An exception to this rule is if the function implementation consists of a single 'throw' statement.
4028+
function checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(decl: FunctionDeclaration, returnType: Type): void {
4029+
// Functions that return 'void' or 'any' don't need any return expressions.
4030+
if (returnType === voidType || returnType === anyType) {
4031+
return;
4032+
}
4033+
4034+
// If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check.
4035+
if (!decl.body || decl.body.kind !== SyntaxKind.FunctionBlock) {
4036+
return;
39954037
}
4038+
4039+
var bodyBlock = <Block>decl.body;
4040+
4041+
// Ensure the body has at least one return expression.
4042+
if (bodyContainsReturnExpressions(bodyBlock)) {
4043+
return;
4044+
}
4045+
4046+
// If there are no return expressions, then we need to check if
4047+
// the function body consists solely of a throw statement;
4048+
// this is to make an exception for unimplemented functions.
4049+
if (bodyContainsSingleThrowStatement(bodyBlock)) {
4050+
return;
4051+
}
4052+
4053+
// This function does not conform to the specification.
4054+
error(decl.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_have_a_return_expression_or_consist_of_a_single_throw_statement);
39964055
}
39974056

39984057
function checkFunctionExpression(node: FunctionExpression, contextualType?: Type, contextualMapper?: TypeMapper): Type {
39994058
// The identityMapper object is used to indicate that function expressions are wildcards
4000-
if (contextualMapper === identityMapper) return anyFunctionType;
4059+
if (contextualMapper === identityMapper) {
4060+
return anyFunctionType;
4061+
}
4062+
40014063
var type = getTypeOfSymbol(node.symbol);
40024064
var links = getNodeLinks(node);
40034065

@@ -4015,6 +4077,9 @@ module ts {
40154077
signature.resolvedReturnType = returnType;
40164078
}
40174079
}
4080+
else {
4081+
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type));
4082+
}
40184083
}
40194084
checkSignatureDeclaration(node);
40204085
if (node.body.kind === SyntaxKind.FunctionBlock) {
@@ -4587,28 +4652,9 @@ module ts {
45874652
}
45884653

45894654
function checkAccessorDeclaration(node: AccessorDeclaration) {
4590-
function checkGetterContainsSingleThrowStatement(node: AccessorDeclaration): boolean {
4591-
var block = <Block>node.body;
4592-
return block.statements.length === 1 && block.statements[0].kind === SyntaxKind.ThrowStatement;
4593-
}
4594-
4595-
function checkGetterReturnsValue(n: Node): boolean {
4596-
switch (n.kind) {
4597-
case SyntaxKind.ReturnStatement:
4598-
return true;
4599-
// do not dive into function-like things - return statements there don't count
4600-
case SyntaxKind.FunctionExpression:
4601-
case SyntaxKind.FunctionDeclaration:
4602-
case SyntaxKind.ArrowFunction:
4603-
case SyntaxKind.ObjectLiteral:
4604-
return false;
4605-
default:
4606-
return forEachChild(n, checkGetterReturnsValue);
4607-
}
4608-
}
46094655
if (node.kind === SyntaxKind.GetAccessor) {
4610-
if (!isInAmbientContext(node) && node.body && !(checkGetterContainsSingleThrowStatement(node) || checkGetterReturnsValue(node))) {
4611-
error(node.name, Diagnostics.Getters_must_return_a_value);
4656+
if (!isInAmbientContext(node) && node.body && !(bodyContainsReturnExpressions(<Block>node.body) || bodyContainsSingleThrowStatement(<Block>node.body))) {
4657+
error(node.name, Diagnostics.A_get_accessor_must_return_a_value_or_consist_of_a_single_throw_statement);
46124658
}
46134659
}
46144660

@@ -4838,7 +4884,7 @@ module ts {
48384884
}
48394885
}
48404886

4841-
// TODO: Check at least one return statement in non-void/any function (except single throw)
4887+
// TODO (drosen): Check at least one return statement in non-void/any function (except single throw)
48424888
}
48434889

48444890
function checkFunctionDeclaration(node: FunctionDeclaration) {
@@ -4850,7 +4896,11 @@ module ts {
48504896
if (node === firstDeclaration) {
48514897
checkFunctionOrConstructorSymbol(symbol);
48524898
}
4899+
48534900
checkSourceElement(node.body);
4901+
if (node.type) {
4902+
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type));
4903+
}
48544904

48554905
// If there is no body and no explicit return type, then report an error.
48564906
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
@@ -113,8 +113,9 @@ module ts {
113113
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." },
114114
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'." },
115115
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" },
116-
Getters_must_return_a_value: { code: 2126, category: DiagnosticCategory.Error, key: "Getters must return a value." },
116+
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." },
117117
Getter_and_setter_accessors_do_not_agree_in_visibility: { code: 2127, category: DiagnosticCategory.Error, key: "Getter and setter accessors do not agree in visibility." },
118+
A_function_whose_declared_type_is_neither_void_nor_any_must_have_a_return_expression_or_consist_of_a_single_throw_statement: { code: 2131, category: DiagnosticCategory.Error, key: "A function whose declared type is neither 'void' nor 'any' must have a 'return' expression or consist of a single 'throw' statement." },
118119
Untyped_function_calls_may_not_accept_type_arguments: { code: 2158, category: DiagnosticCategory.Error, key: "Untyped function calls may not accept type arguments." },
119120
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." },
120121
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
@@ -446,14 +446,18 @@
446446
"category": "Error",
447447
"code": 2119
448448
},
449-
"Getters must return a value.": {
449+
"A 'get' accessor must return a value or consist of a single 'throw' statement.": {
450450
"category": "Error",
451451
"code": 2126
452452
},
453453
"Getter and setter accessors do not agree in visibility.": {
454454
"category": "Error",
455455
"code": 2127
456456
},
457+
"A function whose declared type is neither 'void' nor 'any' must have a 'return' expression or consist of a single 'throw' statement.": {
458+
"category": "Error",
459+
"code": 2131
460+
},
457461
"Untyped function calls may not accept type arguments.": {
458462
"category": "Error",
459463
"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 have a 'return' expression 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 have a 'return' expression 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 have a 'return' expression 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 have a 'return' expression 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 have a 'return' expression 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 have a 'return' expression or consist of a single 'throw' statement.
5052
}
5153

5254
var m;

0 commit comments

Comments
 (0)