Skip to content

Commit d53af09

Browse files
author
Andy
authored
Always check a return expression in a generator (microsoft#20621)
1 parent 3aa192a commit d53af09

6 files changed

+81
-38
lines changed

src/compiler/checker.ts

+39-38
Original file line numberDiff line numberDiff line change
@@ -22199,57 +22199,58 @@ namespace ts {
2219922199

2220022200
function checkReturnStatement(node: ReturnStatement) {
2220122201
// Grammar checking
22202-
if (!checkGrammarStatementInAmbientContext(node)) {
22203-
const functionBlock = getContainingFunction(node);
22204-
if (!functionBlock) {
22205-
grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body);
22206-
}
22202+
if (checkGrammarStatementInAmbientContext(node)) {
22203+
return;
2220722204
}
2220822205

2220922206
const func = getContainingFunction(node);
22210-
if (func) {
22211-
const signature = getSignatureFromDeclaration(func);
22212-
const returnType = getReturnTypeOfSignature(signature);
22213-
const functionFlags = getFunctionFlags(func);
22214-
if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function
22207+
if (!func) {
22208+
grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body);
22209+
return;
22210+
}
22211+
22212+
const signature = getSignatureFromDeclaration(func);
22213+
const returnType = getReturnTypeOfSignature(signature);
22214+
const functionFlags = getFunctionFlags(func);
22215+
const isGenerator = functionFlags & FunctionFlags.Generator;
22216+
if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) {
22217+
const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType;
22218+
if (isGenerator) { // AsyncGenerator function or Generator function
2221522219
// A generator does not need its return expressions checked against its return type.
2221622220
// Instead, the yield expressions are checked against the element type.
22217-
// TODO: Check return expressions of generators when return type tracking is added
22221+
// TODO: Check return types of generators when return type tracking is added
2221822222
// for generators.
2221922223
return;
2222022224
}
22221-
if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) {
22222-
const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType;
22223-
if (func.kind === SyntaxKind.SetAccessor) {
22224-
if (node.expression) {
22225-
error(node, Diagnostics.Setters_cannot_return_a_value);
22226-
}
22225+
else if (func.kind === SyntaxKind.SetAccessor) {
22226+
if (node.expression) {
22227+
error(node, Diagnostics.Setters_cannot_return_a_value);
2222722228
}
22228-
else if (func.kind === SyntaxKind.Constructor) {
22229-
if (node.expression && !checkTypeAssignableTo(exprType, returnType, node)) {
22230-
error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class);
22231-
}
22229+
}
22230+
else if (func.kind === SyntaxKind.Constructor) {
22231+
if (node.expression && !checkTypeAssignableTo(exprType, returnType, node)) {
22232+
error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class);
2223222233
}
22233-
else if (getEffectiveReturnTypeNode(func) || isGetAccessorWithAnnotatedSetAccessor(func)) {
22234-
if (functionFlags & FunctionFlags.Async) { // Async function
22235-
const promisedType = getPromisedTypeOfPromise(returnType);
22236-
const awaitedType = checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
22237-
if (promisedType) {
22238-
// If the function has a return type, but promisedType is
22239-
// undefined, an error will be reported in checkAsyncFunctionReturnType
22240-
// so we don't need to report one here.
22241-
checkTypeAssignableTo(awaitedType, promisedType, node);
22242-
}
22243-
}
22244-
else {
22245-
checkTypeAssignableTo(exprType, returnType, node);
22234+
}
22235+
else if (getEffectiveReturnTypeNode(func) || isGetAccessorWithAnnotatedSetAccessor(func)) {
22236+
if (functionFlags & FunctionFlags.Async) { // Async function
22237+
const promisedType = getPromisedTypeOfPromise(returnType);
22238+
const awaitedType = checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
22239+
if (promisedType) {
22240+
// If the function has a return type, but promisedType is
22241+
// undefined, an error will be reported in checkAsyncFunctionReturnType
22242+
// so we don't need to report one here.
22243+
checkTypeAssignableTo(awaitedType, promisedType, node);
2224622244
}
2224722245
}
22246+
else {
22247+
checkTypeAssignableTo(exprType, returnType, node);
22248+
}
2224822249
}
22249-
else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) {
22250-
// The function has a return type, but the return statement doesn't have an expression.
22251-
error(node, Diagnostics.Not_all_code_paths_return_a_value);
22252-
}
22250+
}
22251+
else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType) && !isGenerator) {
22252+
// The function has a return type, but the return statement doesn't have an expression.
22253+
error(node, Diagnostics.Not_all_code_paths_return_a_value);
2225322254
}
2225422255
}
2225522256

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
tests/cases/compiler/generatorReturnExpressionIsChecked.ts(2,12): error TS2304: Cannot find name 'invalid'.
2+
3+
4+
==== tests/cases/compiler/generatorReturnExpressionIsChecked.ts (1 errors) ====
5+
function* f(): Iterator<number> {
6+
return invalid;
7+
~~~~~~~
8+
!!! error TS2304: Cannot find name 'invalid'.
9+
}
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//// [generatorReturnExpressionIsChecked.ts]
2+
function* f(): Iterator<number> {
3+
return invalid;
4+
}
5+
6+
7+
//// [generatorReturnExpressionIsChecked.js]
8+
function* f() {
9+
return invalid;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
=== tests/cases/compiler/generatorReturnExpressionIsChecked.ts ===
2+
function* f(): Iterator<number> {
3+
>f : Symbol(f, Decl(generatorReturnExpressionIsChecked.ts, 0, 0))
4+
>Iterator : Symbol(Iterator, Decl(lib.es2015.iterable.d.ts, --, --))
5+
6+
return invalid;
7+
}
8+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
=== tests/cases/compiler/generatorReturnExpressionIsChecked.ts ===
2+
function* f(): Iterator<number> {
3+
>f : () => Iterator<number>
4+
>Iterator : Iterator<T>
5+
6+
return invalid;
7+
>invalid : any
8+
}
9+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// @target: esnext
2+
3+
function* f(): Iterator<number> {
4+
return invalid;
5+
}

0 commit comments

Comments
 (0)