Skip to content

Investigate: No floating promises #53146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -23000,6 +23000,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!;
}

function isPromiseType(type: Type): type is TypeReference {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type as TypeReference).target === getGlobalPromiseType(/*reportErrors*/ false));
}

function isArrayType(type: Type): type is TypeReference {
return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type as TypeReference).target === globalArrayType || (type as TypeReference).target === globalReadonlyArrayType);
}
@@ -40818,7 +40822,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Grammar checking
checkGrammarStatementInAmbientContext(node);

checkExpression(node.expression);
const exprType = checkExpression(node.expression);
if (!isAssignmentExpression(node.expression, /*excludeCompoundAssignment*/ true) && isPromiseType(exprType)) {
error(node.expression, Diagnostics.A_Promise_must_be_awaited_returned_or_explicitly_ignored_with_the_void_operator);
}
}

function checkIfStatement(node: IfStatement) {
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
@@ -6427,6 +6427,10 @@
"category": "Error",
"code": 7061
},
"A Promise must be awaited, returned, or explicitly ignored with the 'void' operator": {
"category": "Error",
"code": 7062
},

"You cannot rename this element.": {
"category": "Error",
2 changes: 1 addition & 1 deletion src/server/session.ts
Original file line number Diff line number Diff line change
@@ -2790,7 +2790,7 @@ export class Session<TMessage = string> implements EventSender {
const commands = args.command as CodeActionCommand | CodeActionCommand[]; // They should be sending back the command we sent them.
for (const command of toArray(commands)) {
const { file, project } = this.getFileAndProject(command);
project.getLanguageService().applyCodeActionCommand(command, this.getFormatOptions(file)).then(
void project.getLanguageService().applyCodeActionCommand(command, this.getFormatOptions(file)).then(
_result => { /* TODO: GH#20447 report success message? */ },
_error => { /* TODO: GH#20447 report errors */ });
}
21 changes: 21 additions & 0 deletions tests/baselines/reference/asyncAwaitNestedClasses_es5.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts(15,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts (1 errors) ====
// https://github.com/Microsoft/TypeScript/issues/20744
class A {
static B = class B {
static func2(): Promise<void> {
return new Promise((resolve) => { resolve(null); });
}
static C = class C {
static async func() {
await B.func2();
}
}
}
}

A.B.C.func();
~~~~~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
18 changes: 18 additions & 0 deletions tests/baselines/reference/asyncIIFE.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
tests/cases/compiler/asyncIIFE.ts(2,5): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/compiler/asyncIIFE.ts (1 errors) ====
function f1() {
(async () => {
~~~~~~~~~~~~~~
await 10
~~~~~~~~~~~~~~~~
throw new Error();
~~~~~~~~~~~~~~~~~~~~~~~~~~
})();
~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator

var x = 1;
}

9 changes: 9 additions & 0 deletions tests/baselines/reference/asyncImportNestedYield.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
tests/cases/compiler/asyncImportNestedYield.ts(2,5): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/compiler/asyncImportNestedYield.ts (1 errors) ====
async function* foo() {
import((await import(yield "foo")).default);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
}
8 changes: 7 additions & 1 deletion tests/baselines/reference/awaitedType.errors.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
tests/cases/compiler/awaitedType.ts(22,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/awaitedType.ts(26,12): error TS2589: Type instantiation is excessively deep and possibly infinite.
tests/cases/compiler/awaitedType.ts(234,3): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
tests/cases/compiler/awaitedType.ts(236,28): error TS2493: Tuple type '[number]' of length '1' has no element at index '1'.


==== tests/cases/compiler/awaitedType.ts (3 errors) ====
==== tests/cases/compiler/awaitedType.ts (4 errors) ====
type T1 = Awaited<number>;
type T2 = Awaited<Promise<number>>;
type T3 = Awaited<number | Promise<number>>;
@@ -242,11 +243,16 @@ tests/cases/compiler/awaitedType.ts(236,28): error TS2493: Tuple type '[number]'
const promises = [Promise.resolve(0)] as const

Promise.all(promises).then((results) => {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const first = results[0]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const second = results[1] // error
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~
!!! error TS2493: Tuple type '[number]' of length '1' has no element at index '1'.
})
~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
}

// repro from #40330
Original file line number Diff line number Diff line change
@@ -5,9 +5,11 @@ tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(100,12): error TS2448:
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(111,28): error TS2448: Block-scoped variable 'a' used before its declaration.
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(112,21): error TS2448: Block-scoped variable 'a' used before its declaration.
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(122,22): error TS2448: Block-scoped variable 'a' used before its declaration.
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(127,9): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(130,9): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts (7 errors) ====
==== tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts (9 errors) ====
function foo0() {
let a = x;
~
@@ -156,9 +158,13 @@ tests/cases/compiler/blockScopedVariablesUseBeforeDef.ts(122,22): error TS2448:
function foo17() {
const promise = (async () => {
promise
~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
foo
await null
promise
~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
foo
})()

Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ error TS2468: Cannot find global value 'Promise'.
/main.ts(2,1): error TS1202: Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.
/main.ts(3,1): error TS1203: Export assignment cannot be used when targeting ECMAScript modules. Consider using 'export default' or another module format instead.
/mainJs.js(2,1): error TS2712: A dynamic import call in ES5/ES3 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your '--lib' option.
/mainJs.js(2,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


!!! error TS2468: Cannot find global value 'Promise'.
@@ -20,11 +21,13 @@ error TS2468: Cannot find global value 'Promise'.
export = path; // ok
}

==== /mainJs.js (1 errors) ====
==== /mainJs.js (2 errors) ====
import {} from "./a";
import("./a");
~~~~~~~~~~~~~
!!! error TS2712: A dynamic import call in ES5/ES3 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your '--lib' option.
~~~~~~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
const _ = require("./a"); // No resolution
_.a; // any

15 changes: 14 additions & 1 deletion tests/baselines/reference/callWithSpread4.errors.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
tests/cases/conformance/expressions/functionCalls/callWithSpread4.ts(16,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
tests/cases/conformance/expressions/functionCalls/callWithSpread4.ts(18,5): error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
tests/cases/conformance/expressions/functionCalls/callWithSpread4.ts(27,6): error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
tests/cases/conformance/expressions/functionCalls/callWithSpread4.ts(29,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/conformance/expressions/functionCalls/callWithSpread4.ts (2 errors) ====
==== tests/cases/conformance/expressions/functionCalls/callWithSpread4.ts (4 errors) ====
type R = { a: number }
type W = { b: number }
type RW = { a: number, b: number }
@@ -19,15 +21,24 @@ tests/cases/conformance/expressions/functionCalls/callWithSpread4.ts(27,6): erro
declare var gz: RW[]
declare var fun: (inp: any) => AsyncGenerator<string, void, unknown>
pli(
~~~~
reads,
~~~~~~~~~~
...gun,
~~~~~~~~~~~
~~~~~~
!!! error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
tr,
~~~~~~~
fun,
~~~~~~~~
...gz,
~~~~~~~~~~
writes
~~~~~~~~~~
);
~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator

declare function test(x: any, y: () => string): string | undefined;
declare var anys: any[]
@@ -36,4 +47,6 @@ tests/cases/conformance/expressions/functionCalls/callWithSpread4.ts(27,6): erro
!!! error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.

pli(...[reads, writes, writes] as const)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator

7 changes: 6 additions & 1 deletion tests/baselines/reference/controlFlowIIFE.errors.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
tests/cases/conformance/controlFlow/controlFlowIIFE.ts(64,5): error TS2454: Variable 'v' is used before being assigned.
tests/cases/conformance/controlFlow/controlFlowIIFE.ts(69,5): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
tests/cases/conformance/controlFlow/controlFlowIIFE.ts(72,5): error TS2454: Variable 'v' is used before being assigned.


==== tests/cases/conformance/controlFlow/controlFlowIIFE.ts (2 errors) ====
==== tests/cases/conformance/controlFlow/controlFlowIIFE.ts (3 errors) ====
declare function getStringOrNumber(): string | number;

function f1() {
@@ -74,8 +75,12 @@ tests/cases/conformance/controlFlow/controlFlowIIFE.ts(72,5): error TS2454: Vari
function f6() {
let v: number;
(async function() {
~~~~~~~~~~~~~~~~~~~
v = await 1;
~~~~~~~~~~~~~~~~~~~~
})();
~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
v; // still undefined
~
!!! error TS2454: Variable 'v' is used before being assigned.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
tests/cases/conformance/es6/modules/b.ts(3,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/conformance/es6/modules/a.ts (0 errors) ====
const x = new Promise( ( resolve, reject ) => { resolve( {} ); } );
export default x;

==== tests/cases/conformance/es6/modules/b.ts (1 errors) ====
import x from './a';

( async function() {
~~~~~~~~~~~~~~~~~~~~
const value = await x;
~~~~~~~~~~~~~~~~~~~~~~~~~~
}() );
~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
tests/cases/conformance/es6/modules/b.ts(3,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/conformance/es6/modules/a.ts (0 errors) ====
const x = new Promise( ( resolve, reject ) => { resolve( {} ); } );
export default x;

==== tests/cases/conformance/es6/modules/b.ts (1 errors) ====
import x from './a';

( async function() {
~~~~~~~~~~~~~~~~~~~~
const value = await x;
~~~~~~~~~~~~~~~~~~~~~~~~~~
}() );
~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
tests/cases/compiler/destructureOfVariableSameAsShorthand.ts(10,5): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
tests/cases/compiler/destructureOfVariableSameAsShorthand.ts(14,5): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/compiler/destructureOfVariableSameAsShorthand.ts (2 errors) ====
// https://github.com/microsoft/TypeScript/issues/38969
interface AxiosResponse<T = never> {
data: T;
}

declare function get<T = never, R = AxiosResponse<T>>(): Promise<R>;

async function main() {
// These work examples as expected
get().then((response) => {
~~~~~~~~~~~~~~~~~~~~~~~~~~
// body is never
~~~~~~~~~~~~~~~~~~~~~~~~
const body = response.data;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
})
~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
get().then(({ data }) => {
~~~~~~~~~~~~~~~~~~~~~~~~~~
// data is never
~~~~~~~~~~~~~~~~~~~~~~~~
})
~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
const response = await get()
// body is never
const body = response.data;
// data is never
const { data } = await get<never>();

// The following did not work as expected.
// shouldBeNever should be never, but was any
const { data: shouldBeNever } = await get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
tests/cases/compiler/dynamicImportEvaluateSpecifier.ts(4,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
tests/cases/compiler/dynamicImportEvaluateSpecifier.ts(5,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/compiler/dynamicImportEvaluateSpecifier.ts (2 errors) ====
// https://github.com/microsoft/TypeScript/issues/48285
let i = 0;

import(String(i++));
~~~~~~~~~~~~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
import(String(i++));
~~~~~~~~~~~~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator

const getPath = async () => {
/* in reality this would do some async FS operation, or a web request */
return "/root/my/cool/path";
};

const someFunction = async () => {
const result = await import(await getPath());
};

Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
tests/cases/compiler/dynamicImportTrailingComma.ts(2,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
tests/cases/compiler/dynamicImportTrailingComma.ts(2,12): error TS1009: Trailing comma not allowed.


==== tests/cases/compiler/dynamicImportTrailingComma.ts (1 errors) ====
==== tests/cases/compiler/dynamicImportTrailingComma.ts (2 errors) ====
const path = './foo';
import(path,);
~~~~~~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
~
!!! error TS1009: Trailing comma not allowed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
tests/cases/compiler/dynamicImportWithNestedThis_es2015.ts(11,1): error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator


==== tests/cases/compiler/dynamicImportWithNestedThis_es2015.ts (1 errors) ====
// https://github.com/Microsoft/TypeScript/issues/17564
class C {
private _path = './other';

dynamic() {
return import(this._path);
}
}

const c = new C();
c.dynamic();
~~~~~~~~~~~
!!! error TS7062: A Promise must be awaited, returned, or explicitly ignored with the 'void' operator
Loading