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
Show file tree
Hide file tree
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
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */ });
}
Expand Down
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>>;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
~
Expand Down Expand Up @@ -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
})()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'.
Expand All @@ -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

Expand Down
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 }
Expand All @@ -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[]
Expand All @@ -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() {
Expand Down Expand Up @@ -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.
Expand Down
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