diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 047850c3c30..b0d0e2cbcdb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10860,11 +10860,11 @@ namespace ts { // If return type annotation is omitted check if function has any explicit return statements. // If it does not have any - its inferred return type is void - don't do any checks. // Otherwise get inferred return type from function body and report error only if it is not void / anytype - const inferredReturnType = hasExplicitReturn - ? getReturnTypeOfSignature(getSignatureFromDeclaration(func)) - : voidType; - - if (inferredReturnType === voidType || isTypeAny(inferredReturnType)) { + if (!hasExplicitReturn) { + return; + } + const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); + if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { return; } } @@ -12743,7 +12743,7 @@ namespace ts { return checkNonThenableType(type, location, message); } else { - if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) { + if (type.id === promisedType.id || indexOf(awaitedTypeStack, promisedType.id) >= 0) { // We have a bad actor in the form of a promise whose promised type is // the same promise type, or a mutually recursive promise. Return the // unknown type as we cannot guess the shape. If this were the actual @@ -13896,6 +13896,11 @@ namespace ts { return !!(node.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(getDeclarationOfKind(node.symbol, SyntaxKind.SetAccessor))); } + function isUnwrappedReturnTypeVoidOrAny(func: FunctionLikeDeclaration, returnType: Type): boolean { + const unwrappedReturnType = isAsyncFunctionLike(func) ? getPromisedType(returnType) : returnType; + return maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.Any); + } + function checkReturnStatement(node: ReturnStatement) { // Grammar checking if (!checkGrammarStatementInAmbientContext(node)) { @@ -13944,7 +13949,7 @@ namespace ts { } } } - else if (compilerOptions.noImplicitReturns && !maybeTypeOfKind(returnType, TypeFlags.Void | TypeFlags.Any)) { + else if (compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) { // The function has a return type, but the return statement doesn't have an expression. error(node, Diagnostics.Not_all_code_paths_return_a_value); } diff --git a/tests/baselines/reference/noImplicitReturnsInAsync1.js b/tests/baselines/reference/noImplicitReturnsInAsync1.js new file mode 100644 index 00000000000..6192e9e7f24 --- /dev/null +++ b/tests/baselines/reference/noImplicitReturnsInAsync1.js @@ -0,0 +1,26 @@ +//// [noImplicitReturnsInAsync1.ts] + +async function test(isError: boolean = false) { + if (isError === true) { + return; + } + let x = await Promise.resolve("The test is passed without an error."); +} + +//// [noImplicitReturnsInAsync1.js] +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments)).next()); + }); +}; +function test(isError = false) { + return __awaiter(this, void 0, void 0, function* () { + if (isError === true) { + return; + } + let x = yield Promise.resolve("The test is passed without an error."); + }); +} diff --git a/tests/baselines/reference/noImplicitReturnsInAsync1.symbols b/tests/baselines/reference/noImplicitReturnsInAsync1.symbols new file mode 100644 index 00000000000..b44f17e5fdd --- /dev/null +++ b/tests/baselines/reference/noImplicitReturnsInAsync1.symbols @@ -0,0 +1,17 @@ +=== tests/cases/compiler/noImplicitReturnsInAsync1.ts === + +async function test(isError: boolean = false) { +>test : Symbol(test, Decl(noImplicitReturnsInAsync1.ts, 0, 0)) +>isError : Symbol(isError, Decl(noImplicitReturnsInAsync1.ts, 1, 20)) + + if (isError === true) { +>isError : Symbol(isError, Decl(noImplicitReturnsInAsync1.ts, 1, 20)) + + return; + } + let x = await Promise.resolve("The test is passed without an error."); +>x : Symbol(x, Decl(noImplicitReturnsInAsync1.ts, 5, 7)) +>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Promise : Symbol(Promise, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/noImplicitReturnsInAsync1.types b/tests/baselines/reference/noImplicitReturnsInAsync1.types new file mode 100644 index 00000000000..23f0892f30e --- /dev/null +++ b/tests/baselines/reference/noImplicitReturnsInAsync1.types @@ -0,0 +1,23 @@ +=== tests/cases/compiler/noImplicitReturnsInAsync1.ts === + +async function test(isError: boolean = false) { +>test : (isError?: boolean) => Promise +>isError : boolean +>false : boolean + + if (isError === true) { +>isError === true : boolean +>isError : boolean +>true : boolean + + return; + } + let x = await Promise.resolve("The test is passed without an error."); +>x : string +>await Promise.resolve("The test is passed without an error.") : string +>Promise.resolve("The test is passed without an error.") : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>"The test is passed without an error." : string +} diff --git a/tests/baselines/reference/noImplicitReturnsInAsync2.errors.txt b/tests/baselines/reference/noImplicitReturnsInAsync2.errors.txt new file mode 100644 index 00000000000..83673ff518b --- /dev/null +++ b/tests/baselines/reference/noImplicitReturnsInAsync2.errors.txt @@ -0,0 +1,45 @@ +tests/cases/compiler/noImplicitReturnsInAsync2.ts(3,16): error TS7030: Not all code paths return a value. +tests/cases/compiler/noImplicitReturnsInAsync2.ts(25,48): error TS7030: Not all code paths return a value. + + +==== tests/cases/compiler/noImplicitReturnsInAsync2.ts (2 errors) ==== + + // Should be an error, Promise, currently retorted correctly + async function test3(isError: boolean = true) { + ~~~~~ +!!! error TS7030: Not all code paths return a value. + if (isError === true) { + return 6; + } + } + + // Should not be an error, Promise, currently **not** working + async function test4(isError: boolean = true) { + if (isError === true) { + return undefined; + } + } + + // should not be error, Promise currently working correctly + async function test5(isError: boolean = true): Promise { //should not be error + if (isError === true) { + return undefined; + } + } + + + // should be error, currently reported correctly + async function test6(isError: boolean = true): Promise { + ~~~~~~~~~~~~~~~ +!!! error TS7030: Not all code paths return a value. + if (isError === true) { + return undefined; + } + } + + // infered to be Promise, should not be an error, currently reported correctly + async function test7(isError: boolean = true) { + if (isError === true) { + return; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/noImplicitReturnsInAsync2.js b/tests/baselines/reference/noImplicitReturnsInAsync2.js new file mode 100644 index 00000000000..5e034237fef --- /dev/null +++ b/tests/baselines/reference/noImplicitReturnsInAsync2.js @@ -0,0 +1,87 @@ +//// [noImplicitReturnsInAsync2.ts] + +// Should be an error, Promise, currently retorted correctly +async function test3(isError: boolean = true) { + if (isError === true) { + return 6; + } +} + +// Should not be an error, Promise, currently **not** working +async function test4(isError: boolean = true) { + if (isError === true) { + return undefined; + } +} + +// should not be error, Promise currently working correctly +async function test5(isError: boolean = true): Promise { //should not be error + if (isError === true) { + return undefined; + } +} + + +// should be error, currently reported correctly +async function test6(isError: boolean = true): Promise { + if (isError === true) { + return undefined; + } +} + +// infered to be Promise, should not be an error, currently reported correctly +async function test7(isError: boolean = true) { + if (isError === true) { + return; + } +} + +//// [noImplicitReturnsInAsync2.js] +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments)).next()); + }); +}; +// Should be an error, Promise, currently retorted correctly +function test3(isError = true) { + return __awaiter(this, void 0, void 0, function* () { + if (isError === true) { + return 6; + } + }); +} +// Should not be an error, Promise, currently **not** working +function test4(isError = true) { + return __awaiter(this, void 0, void 0, function* () { + if (isError === true) { + return undefined; + } + }); +} +// should not be error, Promise currently working correctly +function test5(isError = true) { + return __awaiter(this, void 0, void 0, function* () { + if (isError === true) { + return undefined; + } + }); +} +// should be error, currently reported correctly +function test6(isError = true) { + return __awaiter(this, void 0, void 0, function* () { + if (isError === true) { + return undefined; + } + }); +} +// infered to be Promise, should not be an error, currently reported correctly +function test7(isError = true) { + return __awaiter(this, void 0, void 0, function* () { + if (isError === true) { + return; + } + }); +} diff --git a/tests/cases/compiler/noImplicitReturnsInAsync1.ts b/tests/cases/compiler/noImplicitReturnsInAsync1.ts new file mode 100644 index 00000000000..a4aadd81f75 --- /dev/null +++ b/tests/cases/compiler/noImplicitReturnsInAsync1.ts @@ -0,0 +1,9 @@ +// @target: es6 +// @noImplicitReturns: true + +async function test(isError: boolean = false) { + if (isError === true) { + return; + } + let x = await Promise.resolve("The test is passed without an error."); +} \ No newline at end of file diff --git a/tests/cases/compiler/noImplicitReturnsInAsync2.ts b/tests/cases/compiler/noImplicitReturnsInAsync2.ts new file mode 100644 index 00000000000..20488b6bd37 --- /dev/null +++ b/tests/cases/compiler/noImplicitReturnsInAsync2.ts @@ -0,0 +1,38 @@ +// @target: es6 +// @noImplicitReturns: true + +// Should be an error, Promise, currently retorted correctly +async function test3(isError: boolean = true) { + if (isError === true) { + return 6; + } +} + +// Should not be an error, Promise, currently **not** working +async function test4(isError: boolean = true) { + if (isError === true) { + return undefined; + } +} + +// should not be error, Promise currently working correctly +async function test5(isError: boolean = true): Promise { //should not be error + if (isError === true) { + return undefined; + } +} + + +// should be error, currently reported correctly +async function test6(isError: boolean = true): Promise { + if (isError === true) { + return undefined; + } +} + +// infered to be Promise, should not be an error, currently reported correctly +async function test7(isError: boolean = true) { + if (isError === true) { + return; + } +} \ No newline at end of file