Skip to content

Commit e9b3fe9

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 df5c254 commit e9b3fe9

File tree

46 files changed

+402
-117
lines changed

Some content is hidden

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

46 files changed

+402
-117
lines changed

src/compiler/checker.ts

Lines changed: 89 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3794,7 +3794,7 @@ module ts {
37943794
}
37953795
return checkUntypedCall(node);
37963796
}
3797-
// If FuncExpr’s apparent type(section 3.8.1) is a function type, the call is a typed function call.
3797+
// If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call.
37983798
// TypeScript employs overload resolution in typed function calls in order to support functions
37993799
// with multiple call signatures.
38003800
if (!signatures.length) {
@@ -3826,7 +3826,7 @@ module ts {
38263826
return checkUntypedCall(node);
38273827
}
38283828

3829-
// If ConstructExpr’s apparent type(section 3.8.1) is an object type with one or
3829+
// If ConstructExpr's apparent type(section 3.8.1) is an object type with one or
38303830
// more construct signatures, the expression is processed in the same manner as a
38313831
// function call, but using the construct signatures as the initial set of candidate
38323832
// signatures for overload resolution.The result type of the function call becomes
@@ -3847,7 +3847,7 @@ module ts {
38473847
return checkCall(node, constructSignatures);
38483848
}
38493849

3850-
// If ConstructExpr’s apparent type is an object type with no construct signatures but
3850+
// If ConstructExpr's apparent type is an object type with no construct signatures but
38513851
// one or more call signatures, the expression is processed as a function call. A compile-time
38523852
// error occurs if the result of the function call is not Void. The type of the result of the
38533853
// operation is Any.
@@ -3931,12 +3931,10 @@ module ts {
39313931
}
39323932

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

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

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

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

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

@@ -4014,6 +4076,9 @@ module ts {
40144076
signature.resolvedReturnType = returnType;
40154077
}
40164078
}
4079+
else {
4080+
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type));
4081+
}
40174082
}
40184083
checkSignatureDeclaration(node);
40194084
if (node.body.kind === SyntaxKind.FunctionBlock) {
@@ -4586,28 +4651,9 @@ module ts {
45864651
}
45874652

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

@@ -4843,7 +4889,7 @@ module ts {
48434889
}
48444890
}
48454891

4846-
// TODO: Check at least one return statement in non-void/any function (except single throw)
4892+
// TODO (drosen): Check at least one return statement in non-void/any function (except single throw)
48474893
}
48484894

48494895
function checkFunctionDeclaration(node: FunctionDeclaration) {
@@ -4856,7 +4902,11 @@ module ts {
48564902
if (node === firstDeclaration) {
48574903
checkFunctionOrConstructorSymbol(symbol);
48584904
}
4905+
48594906
checkSourceElement(node.body);
4907+
if (node.type) {
4908+
checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type));
4909+
}
48604910

48614911
// If there is no body and no explicit return type, then report an error.
48624912
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
@@ -1029,7 +1029,7 @@ module ts {
10291029
return finishNode(node);
10301030
}
10311031

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

src/compiler/scanner.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -142,18 +142,18 @@ module ts {
142142
As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers
143143
IdentifierStart ::
144144
Can contain Unicode 3.0.0 categories:
145-
Uppercase letter (Lu),
146-
Lowercase letter (Ll),
147-
Titlecase letter (Lt),
148-
Modifier letter (Lm),
149-
Other letter (Lo), or
150-
Letter number (Nl).
145+
"Uppercase letter (Lu)",
146+
"Lowercase letter (Ll)",
147+
"Titlecase letter (Lt)",
148+
"Modifier letter (Lm)",
149+
"Other letter (Lo)", or
150+
"Letter number (Nl)".
151151
IdentifierPart :: =
152152
Can contain IdentifierStart + Unicode 3.0.0 categories:
153-
Non-spacing mark (Mn),
154-
Combining spacing mark (Mc),
155-
Decimal number (Nd), or
156-
Connector punctuation (Pc).
153+
"Non-spacing mark (Mn)",
154+
"Combining spacing mark (Mc)",
155+
"Decimal number (Nd)", or
156+
"Connector punctuation (Pc)".
157157
158158
Codepoint ranges for ES3 Identifiers are extracted from the Unicode 3.0.0 specification at:
159159
http://www.unicode.org/Public/3.0-Update/UnicodeData-3.0.0.txt
@@ -165,18 +165,18 @@ module ts {
165165
As per ECMAScript Language Specification 5th Edition, Section 7.6: ISyntaxToken Names and Identifiers
166166
IdentifierStart ::
167167
Can contain Unicode 6.2 categories:
168-
Uppercase letter (Lu),
169-
Lowercase letter (Ll),
170-
Titlecase letter (Lt),
171-
Modifier letter (Lm),
172-
Other letter (Lo), or
173-
Letter number (Nl).
168+
"Uppercase letter (Lu)",
169+
"Lowercase letter (Ll)",
170+
"Titlecase letter (Lt)",
171+
"Modifier letter (Lm)",
172+
"Other letter (Lo)", or
173+
"Letter number (Nl)".
174174
IdentifierPart ::
175175
Can contain IdentifierStart + Unicode 6.2 categories:
176-
Non-spacing mark (Mn),
177-
Combining spacing mark (Mc),
178-
Decimal number (Nd),
179-
Connector punctuation (Pc),
176+
"Non-spacing mark (Mn)",
177+
"Combining spacing mark (Mc)",
178+
"Decimal number (Nd)",
179+
"Connector punctuation (Pc)",
180180
<ZWNJ>, or
181181
<ZWJ>.
182182

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)