diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 37163e9e8827b..1f7a908e2e5c1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19388,7 +19388,18 @@ namespace ts { // Grammar checking checkGrammarStatementInAmbientContext(node); - checkExpression(node.expression); + const type = checkExpression(node.expression); + + // Check for missing 'await' keyword in async functions + if (type && + type.symbol === getGlobalPromiseType(/*reportErrors*/ false).symbol && + node.expression.kind === ts.SyntaxKind.CallExpression) { + // Calls to async functions without await inside async functions are disallowed + const container = getContainingFunction(node); + if (container && isAsyncFunction(container)) { + error(node.expression, Diagnostics.Return_value_of_async_function_call_was_discarded_Did_you_mean_to_await_its_result); + } + } } function checkIfStatement(node: IfStatement) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 10dc25ef70e61..355defa554c3e 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2107,6 +2107,10 @@ "category": "Error", "code": 2709 }, + "Return value of async function call was discarded. Did you mean to 'await' its result?": { + "category": "Error", + "code": 2710 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/missedAwait.errors.txt b/tests/baselines/reference/missedAwait.errors.txt new file mode 100644 index 0000000000000..bafd2356dcf78 --- /dev/null +++ b/tests/baselines/reference/missedAwait.errors.txt @@ -0,0 +1,36 @@ +tests/cases/compiler/missedAwait.ts(18,5): error TS2710: Return value of async function call was discarded. Did you mean to 'await' its result? +tests/cases/compiler/missedAwait.ts(19,5): error TS2710: Return value of async function call was discarded. Did you mean to 'await' its result? + + +==== tests/cases/compiler/missedAwait.ts (2 errors) ==== + async function isAsync() { + return 10; + } + + class SomeClass { + async foo() { + return "ok"; + } + } + + var x = new SomeClass(); + function notAsync() { + isAsync(); // OK + x.foo(); // OK + } + + async function alsoAsync() { + isAsync(); // No + ~~~~~~~~~ +!!! error TS2710: Return value of async function call was discarded. Did you mean to 'await' its result? + x.foo(); // No + ~~~~~~~ +!!! error TS2710: Return value of async function call was discarded. Did you mean to 'await' its result? + void isAsync(); // OK + void x.foo(); // OK + + var j = x.foo(); // OK + j = x.foo(); // OK + await isAsync(); // OK + await x.foo(); // OK + } \ No newline at end of file diff --git a/tests/baselines/reference/missedAwait.js b/tests/baselines/reference/missedAwait.js new file mode 100644 index 0000000000000..d1d4dc82fbb60 --- /dev/null +++ b/tests/baselines/reference/missedAwait.js @@ -0,0 +1,67 @@ +//// [missedAwait.ts] +async function isAsync() { + return 10; +} + +class SomeClass { + async foo() { + return "ok"; + } +} + +var x = new SomeClass(); +function notAsync() { + isAsync(); // OK + x.foo(); // OK +} + +async function alsoAsync() { + isAsync(); // No + x.foo(); // No + void isAsync(); // OK + void x.foo(); // OK + + var j = x.foo(); // OK + j = x.foo(); // OK + await isAsync(); // OK + await x.foo(); // OK +} + +//// [missedAwait.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 isAsync() { + return __awaiter(this, void 0, void 0, function* () { + return 10; + }); +} +class SomeClass { + foo() { + return __awaiter(this, void 0, void 0, function* () { + return "ok"; + }); + } +} +var x = new SomeClass(); +function notAsync() { + isAsync(); // OK + x.foo(); // OK +} +function alsoAsync() { + return __awaiter(this, void 0, void 0, function* () { + isAsync(); // No + x.foo(); // No + void isAsync(); // OK + void x.foo(); // OK + var j = x.foo(); // OK + j = x.foo(); // OK + yield isAsync(); // OK + yield x.foo(); // OK + }); +} diff --git a/tests/cases/compiler/missedAwait.ts b/tests/cases/compiler/missedAwait.ts new file mode 100644 index 0000000000000..1c57ab18a5e11 --- /dev/null +++ b/tests/cases/compiler/missedAwait.ts @@ -0,0 +1,29 @@ +// @target: es6 + +async function isAsync() { + return 10; +} + +class SomeClass { + async foo() { + return "ok"; + } +} + +var x = new SomeClass(); +function notAsync() { + isAsync(); // OK + x.foo(); // OK +} + +async function alsoAsync() { + isAsync(); // No + x.foo(); // No + void isAsync(); // OK + void x.foo(); // OK + + var j = x.foo(); // OK + j = x.foo(); // OK + await isAsync(); // OK + await x.foo(); // OK +} \ No newline at end of file